[gtksourceview: 2/3] hover: add GtkSourceHover support




commit a074ac96297d5172294f09ddc63752bc49610f83
Author: Christian Hergert <chergert redhat com>
Date:   Mon Mar 8 16:13:14 2021 -0800

    hover: add GtkSourceHover support
    
    This adds support for a GtkSourceHover, GtkSourceHoverProvider,
    GtkSourceHoverContext, and GtkSourceHoverDisplay objects. These can
    be used to provide interactive tooltips to code similar to those
    used by various editors integrating with language servers.
    
    Applications can register GtkSourceHoverProviders which can populate the
    contents of a GtkSourceHoverDisplay by adding widgets to it from their
    synchronous populate, or asynchronous populate_async() callbacks.

 docs/reference/gtksourceview-5.0-sections.txt   |  66 ++++
 docs/reference/gtksourceview-docs.xml.in        |   8 +
 gtksourceview/gtksource.h                       |   4 +
 gtksourceview/gtksourcehover-private.h          |  30 ++
 gtksourceview/gtksourcehover.c                  | 415 ++++++++++++++++++++++++
 gtksourceview/gtksourcehover.h                  |  46 +++
 gtksourceview/gtksourcehoverassistant-private.h |  43 +++
 gtksourceview/gtksourcehoverassistant.c         | 285 ++++++++++++++++
 gtksourceview/gtksourcehovercontext-private.h   |  44 +++
 gtksourceview/gtksourcehovercontext.c           | 351 ++++++++++++++++++++
 gtksourceview/gtksourcehovercontext.h           |  51 +++
 gtksourceview/gtksourcehoverdisplay-private.h   |  30 ++
 gtksourceview/gtksourcehoverdisplay.c           | 142 ++++++++
 gtksourceview/gtksourcehoverdisplay.h           |  53 +++
 gtksourceview/gtksourcehoverprovider.c          | 120 +++++++
 gtksourceview/gtksourcehoverprovider.h          |  70 ++++
 gtksourceview/gtksourcetypes.h                  |   4 +
 gtksourceview/gtksourceview.c                   |  33 ++
 gtksourceview/gtksourceview.h                   |   2 +
 gtksourceview/meson.build                       |   9 +
 tests/test-widget.c                             |  75 ++++-
 tests/test-widget.ui                            |  11 +
 22 files changed, 1890 insertions(+), 2 deletions(-)
---
diff --git a/docs/reference/gtksourceview-5.0-sections.txt b/docs/reference/gtksourceview-5.0-sections.txt
index 0c6727e6..3ea1e2e4 100644
--- a/docs/reference/gtksourceview-5.0-sections.txt
+++ b/docs/reference/gtksourceview-5.0-sections.txt
@@ -434,6 +434,72 @@ GTK_SOURCE_TYPE_GUTTER_RENDERER_TEXT
 gtk_source_gutter_renderer_text_get_type
 </SECTION>
 
+<SECTION>
+<FILE>hover</FILE>
+GtkSourceHover
+<SUBSECTION>
+gtk_source_hover_add_provider
+gtk_source_hover_remove_provider
+<SUBSECTION Standard>
+GTK_SOURCE_IS_HOVER
+GTK_SOURCE_IS_HOVER_CLASS
+GTK_SOURCE_HOVER
+GTK_SOURCE_HOVER_CLASS
+GTK_SOURCE_HOVER_GET_CLASS
+GTK_SOURCE_TYPE_HOVER
+gtk_source_hover_get_type
+</SECTION>
+
+<SECTION>
+<FILE>hovercontext</FILE>
+GtkSourceHoverContext
+<SUBSECTION>
+gtk_source_hover_context_get_bounds
+gtk_source_hover_context_get_buffer
+gtk_source_hover_context_get_iter
+gtk_source_hover_context_get_view
+<SUBSECTION Standard>
+GTK_SOURCE_IS_HOVER_CONTEXT
+GTK_SOURCE_IS_HOVER_CONTEXT_CLASS
+GTK_SOURCE_HOVER_CONTEXT
+GTK_SOURCE_HOVER_CONTEXT_CLASS
+GTK_SOURCE_HOVER_CONTEXT_GET_CLASS
+GTK_SOURCE_TYPE_HOVER_CONTEXT
+gtk_source_hover_context_get_type
+</SECTION>
+
+<SECTION>
+<FILE>hoverdisplay</FILE>
+GtkSourceHoverDisplay
+<SUBSECTION>
+gtk_source_hover_display_append
+gtk_source_hover_display_prepend
+gtk_source_hover_display_insert_after
+gtk_source_hover_display_remove
+<SUBSECTION Standard>
+GTK_SOURCE_IS_HOVER_DISPLAY
+GTK_SOURCE_IS_HOVER_DISPLAY_CLASS
+GTK_SOURCE_HOVER_DISPLAY
+GTK_SOURCE_HOVER_DISPLAY_CLASS
+GTK_SOURCE_HOVER_DISPLAY_GET_CLASS
+GTK_SOURCE_TYPE_HOVER_DISPLAY
+gtk_source_hover_display_get_type
+</SECTION>
+
+<SECTION>
+<FILE>hoverprovider</FILE>
+GtkSourceHoverProvider
+GtkSourceHoverProviderInterface
+gtk_source_hover_provider_populate_async
+gtk_source_hover_provider_populate_finish
+<SUBSECTION Standard>
+GTK_SOURCE_IS_HOVER_PROVIDER
+GTK_SOURCE_HOVER_PROVIDER
+GTK_SOURCE_HOVER_PROVIDER_GET_IFACE
+GTK_SOURCE_TYPE_HOVER_PROVIDER
+gtk_source_hover_provider_get_type
+</SECTION>
+
 <SECTION>
 <FILE>init</FILE>
 <TITLE>GtkSourceView Initialization and Finalization</TITLE>
diff --git a/docs/reference/gtksourceview-docs.xml.in b/docs/reference/gtksourceview-docs.xml.in
index e6be26f4..067e936d 100644
--- a/docs/reference/gtksourceview-docs.xml.in
+++ b/docs/reference/gtksourceview-docs.xml.in
@@ -71,6 +71,14 @@
       <xi:include href="xml/markattributes.xml"/>
     </chapter>
 
+    <chapter id="hover">
+      <title>Interactive Tooltips</title>
+      <xi:include href="xml/hover.xml"/>
+      <xi:include href="xml/hovercontext.xml"/>
+      <xi:include href="xml/hoverdisplay.xml"/>
+      <xi:include href="xml/hoverprovider.xml"/>
+    </chapter>
+
     <chapter id="printing">
       <title>Printing</title>
       <xi:include href="xml/printcompositor.xml"/>
diff --git a/gtksourceview/gtksource.h b/gtksourceview/gtksource.h
index 31942337..45820372 100644
--- a/gtksourceview/gtksource.h
+++ b/gtksourceview/gtksource.h
@@ -34,6 +34,10 @@
 #include "gtksourcegutterrenderer.h"
 #include "gtksourcegutterrenderertext.h"
 #include "gtksourcegutterrendererpixbuf.h"
+#include "gtksourcehover.h"
+#include "gtksourcehovercontext.h"
+#include "gtksourcehoverdisplay.h"
+#include "gtksourcehoverprovider.h"
 #include "gtksourceinit.h"
 #include "gtksourcelanguage.h"
 #include "gtksourcelanguagemanager.h"
diff --git a/gtksourceview/gtksourcehover-private.h b/gtksourceview/gtksourcehover-private.h
new file mode 100644
index 00000000..ad13eddb
--- /dev/null
+++ b/gtksourceview/gtksourcehover-private.h
@@ -0,0 +1,30 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#include "gtksourcehover.h"
+
+G_BEGIN_DECLS
+
+GtkSourceHover *_gtk_source_hover_new (GtkSourceView *view);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcehover.c b/gtksourceview/gtksourcehover.c
new file mode 100644
index 00000000..7cebc680
--- /dev/null
+++ b/gtksourceview/gtksourcehover.c
@@ -0,0 +1,415 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gtksourceassistant-private.h"
+#include "gtksourcehover-private.h"
+#include "gtksourcehoverassistant-private.h"
+#include "gtksourcehovercontext.h"
+#include "gtksourcehoverprovider.h"
+#include "gtksourceiter-private.h"
+#include "gtksourcesignalgroup-private.h"
+#include "gtksourceview-private.h"
+
+#define DEFAULT_HOVER_DELAY 500
+
+/**
+ * SECTION:hover
+ * @Title: GtkSourceHover
+ * @Short_description: Interactive tooltips
+ * @See_also: #GtkSourceHoverProvider, #GtkSourceHoverDisplay, and
+ *   #GtkSourceHoverContext.
+ *
+ * #GtkSourceHover allows a #GtkSourceView to provide contextual information.
+ * When enabled, if the user hovers over a word in the text editor, a series
+ * of registered #GtkSourceHoverProvider can populate a #GtkSourceHoverDisplay
+ * with useful information.
+ *
+ * To enable call gtk_source_view_get_hover() and add #GtkSourceHoverProvider
+ * using gtk_source_hover_add_provider(). To disable, remove all registered
+ * providers with gtk_source_hover_remove_provider().
+ *
+ * You can change how long to wait to display the interactive tooltip by
+ * setting the #GtkSourceHover:hover-delay property in milliseconds.
+ *
+ * Since: 5.0
+ */
+
+struct _GtkSourceHover
+{
+       GObject             parent_instance;
+
+       GtkSourceView      *view;
+       GtkSourceAssistant *assistant;
+
+       GPtrArray          *providers;
+
+       double              motion_x;
+       double              motion_y;
+
+       guint               hover_delay;
+       guint               settle_source;
+};
+
+G_DEFINE_TYPE (GtkSourceHover, gtk_source_hover, G_TYPE_OBJECT)
+
+enum {
+       PROP_0,
+       PROP_HOVER_DELAY,
+       N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static gboolean
+gtk_source_hover_get_bounds (GtkSourceHover *self,
+                             GtkTextIter    *begin,
+                             GtkTextIter    *end,
+                             GtkTextIter    *location)
+{
+       GtkTextIter iter;
+       int x, y;
+
+       g_assert (GTK_SOURCE_IS_HOVER (self));
+       g_assert (!self->view || GTK_SOURCE_IS_VIEW (self->view));
+       g_assert (begin != NULL);
+       g_assert (end != NULL);
+       g_assert (location != NULL);
+
+       memset (begin, 0, sizeof *begin);
+       memset (end, 0, sizeof *end);
+       memset (location, 0, sizeof *location);
+
+       if (self->view == NULL)
+       {
+               return FALSE;
+       }
+
+       g_assert (GTK_SOURCE_IS_VIEW (self->view));
+
+       gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (self->view),
+                                              GTK_TEXT_WINDOW_WIDGET,
+                                              self->motion_x,
+                                              self->motion_y,
+                                              &x, &y);
+
+       if (!gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (self->view), &iter, x, y))
+       {
+               return FALSE;
+       }
+
+       *location = iter;
+
+       if (!_gtk_source_iter_inside_word (&iter))
+       {
+               *begin = iter;
+               gtk_text_iter_set_line_offset (begin, 0);
+
+               *end = iter;
+               gtk_text_iter_forward_to_line_end (end);
+
+               return TRUE;
+       }
+
+       if (!_gtk_source_iter_starts_full_word (&iter))
+       {
+               _gtk_source_iter_backward_extra_natural_word_start (&iter);
+       }
+
+       *begin = iter;
+       *end = iter;
+
+       _gtk_source_iter_forward_extra_natural_word_end (end);
+
+       return TRUE;
+}
+
+static gboolean
+gtk_source_hover_settled_cb (GtkSourceHover *self)
+{
+       GtkTextIter begin;
+       GtkTextIter end;
+       GtkTextIter location;
+
+       g_assert (GTK_SOURCE_IS_HOVER (self));
+
+       self->settle_source = 0;
+
+       if (gtk_source_hover_get_bounds (self, &begin, &end, &location))
+       {
+               _gtk_source_hover_assistant_display (GTK_SOURCE_HOVER_ASSISTANT (self->assistant),
+                                                    (GtkSourceHoverProvider **)self->providers->pdata,
+                                                    self->providers->len,
+                                                    &begin, &end, &location);
+       }
+
+       return G_SOURCE_REMOVE;
+}
+
+static void
+gtk_source_hover_queue_settle (GtkSourceHover *self)
+{
+       g_assert (GTK_SOURCE_IS_HOVER (self));
+
+       g_clear_handle_id (&self->settle_source, g_source_remove);
+       self->settle_source = g_timeout_add (self->hover_delay,
+                                            (GSourceFunc) gtk_source_hover_settled_cb,
+                                            self);
+}
+
+static gboolean
+gtk_source_hover_key_pressed_cb (GtkSourceHover        *self,
+                                 guint                  keyval,
+                                 guint                  keycode,
+                                 GdkModifierType        state,
+                                 GtkEventControllerKey *controller)
+{
+       g_assert (GTK_SOURCE_IS_HOVER (self));
+       g_assert (GTK_IS_EVENT_CONTROLLER_KEY (controller));
+
+       g_clear_handle_id (&self->settle_source, g_source_remove);
+       _gtk_source_hover_assistant_dismiss (GTK_SOURCE_HOVER_ASSISTANT (self->assistant));
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+
+static void
+gtk_source_hover_motion_cb (GtkSourceHover           *self,
+                            double                    x,
+                            double                    y,
+                            GtkEventControllerMotion *controller)
+{
+       g_assert (GTK_SOURCE_IS_HOVER (self));
+       g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (controller));
+
+       self->motion_x = x;
+       self->motion_y = y;
+
+       gtk_source_hover_queue_settle (self);
+}
+
+static void
+gtk_source_hover_leave_cb (GtkSourceHover           *self,
+                           GtkEventControllerMotion *controller)
+{
+       g_assert (GTK_SOURCE_IS_HOVER (self));
+       g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (controller));
+
+       g_clear_handle_id (&self->settle_source, g_source_remove);
+}
+
+static gboolean
+gtk_source_hover_scroll_cb (GtkSourceHover           *self,
+                            double                    dx,
+                            double                    dy,
+                            GtkEventControllerScroll *controller)
+{
+       g_assert (GTK_SOURCE_IS_HOVER (self));
+       g_assert (GTK_IS_EVENT_CONTROLLER_SCROLL (controller));
+
+       g_clear_handle_id (&self->settle_source, g_source_remove);
+       _gtk_source_hover_assistant_dismiss (GTK_SOURCE_HOVER_ASSISTANT (self->assistant));
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+static void
+gtk_source_hover_dispose (GObject *object)
+{
+       GtkSourceHover *self = (GtkSourceHover *)object;
+
+       g_clear_handle_id (&self->settle_source, g_source_remove);
+       g_clear_pointer (&self->assistant, _gtk_source_assistant_destroy);
+       g_clear_weak_pointer (&self->view);
+
+       if (self->providers->len > 0)
+       {
+               g_ptr_array_remove_range (self->providers, 0, self->providers->len);
+       }
+
+       G_OBJECT_CLASS (gtk_source_hover_parent_class)->dispose (object);
+}
+
+static void
+gtk_source_hover_finalize (GObject *object)
+{
+       GtkSourceHover *self = (GtkSourceHover *)object;
+
+       g_clear_pointer (&self->providers, g_ptr_array_unref);
+
+       g_assert (self->assistant == NULL);
+       g_assert (self->settle_source == 0);
+
+       G_OBJECT_CLASS (gtk_source_hover_parent_class)->finalize (object);
+}
+
+static void
+gtk_source_hover_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+       GtkSourceHover *self = GTK_SOURCE_HOVER (object);
+
+       switch (prop_id) {
+       case PROP_HOVER_DELAY:
+               g_value_set_uint (value, self->hover_delay);
+               break;
+
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       }
+}
+
+static void
+gtk_source_hover_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+       GtkSourceHover *self = GTK_SOURCE_HOVER (object);
+
+       switch (prop_id) {
+       case PROP_HOVER_DELAY:
+               self->hover_delay = g_value_get_uint (value);
+               break;
+
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       }
+}
+
+static void
+gtk_source_hover_class_init (GtkSourceHoverClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->dispose = gtk_source_hover_dispose;
+       object_class->finalize = gtk_source_hover_finalize;
+       object_class->get_property = gtk_source_hover_get_property;
+       object_class->set_property = gtk_source_hover_set_property;
+
+       /**
+        * GtkSourceHover:hover-delay:
+        *
+        * The "hover-delay" property contains the number of milliseconds to
+        * delay before showing the hover assistant.
+        */
+       properties [PROP_HOVER_DELAY] =
+               g_param_spec_uint ("hover-delay",
+                                  "Hover Delay",
+                                  "Number of milliseconds to delay before showing assistant",
+                                  1, 5000, DEFAULT_HOVER_DELAY,
+                                  (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gtk_source_hover_init (GtkSourceHover *self)
+{
+       self->providers = g_ptr_array_new_with_free_func (g_object_unref);
+       self->hover_delay = DEFAULT_HOVER_DELAY;
+}
+
+GtkSourceHover *
+_gtk_source_hover_new (GtkSourceView *view)
+{
+       GtkSourceHover *self;
+       GtkEventController *key;
+       GtkEventController *motion;
+       GtkEventController *scroll;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), NULL);
+
+       self = g_object_new (GTK_SOURCE_TYPE_HOVER, NULL);
+       g_set_weak_pointer (&self->view, view);
+       self->assistant = _gtk_source_hover_assistant_new ();
+       _gtk_source_view_add_assistant (view, self->assistant);
+
+       key = gtk_event_controller_key_new ();
+       g_signal_connect_object (key,
+                                "key-pressed",
+                                G_CALLBACK (gtk_source_hover_key_pressed_cb),
+                                self,
+                                G_CONNECT_SWAPPED);
+       gtk_widget_add_controller (GTK_WIDGET (view), key);
+
+       motion = gtk_event_controller_motion_new ();
+       g_signal_connect_object (motion,
+                                "leave",
+                                G_CALLBACK (gtk_source_hover_leave_cb),
+                                self,
+                                G_CONNECT_SWAPPED);
+       g_signal_connect_object (motion,
+                                "motion",
+                                G_CALLBACK (gtk_source_hover_motion_cb),
+                                self,
+                                G_CONNECT_SWAPPED);
+       gtk_widget_add_controller (GTK_WIDGET (view), motion);
+
+       scroll = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
+       g_signal_connect_object (scroll,
+                                "scroll",
+                                G_CALLBACK (gtk_source_hover_scroll_cb),
+                                self,
+                                G_CONNECT_SWAPPED);
+       gtk_widget_add_controller (GTK_WIDGET (view), scroll);
+
+       return self;
+}
+
+void
+gtk_source_hover_add_provider (GtkSourceHover         *self,
+                               GtkSourceHoverProvider *provider)
+{
+       g_return_if_fail (GTK_SOURCE_IS_HOVER (self));
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_PROVIDER (provider));
+
+       for (guint i = 0; i < self->providers->len; i++)
+       {
+               if (g_ptr_array_index (self->providers, i) == (gpointer)provider)
+               {
+                       return;
+               }
+       }
+
+       g_ptr_array_add (self->providers, g_object_ref (provider));
+}
+
+void
+gtk_source_hover_remove_provider (GtkSourceHover         *self,
+                                  GtkSourceHoverProvider *provider)
+{
+       g_return_if_fail (GTK_SOURCE_IS_HOVER (self));
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_PROVIDER (provider));
+
+       for (guint i = 0; i < self->providers->len; i++)
+       {
+               if (g_ptr_array_index (self->providers, i) == (gpointer)provider)
+               {
+                       g_ptr_array_remove_index (self->providers, i);
+                       return;
+               }
+       }
+}
diff --git a/gtksourceview/gtksourcehover.h b/gtksourceview/gtksourcehover.h
new file mode 100644
index 00000000..56d94b78
--- /dev/null
+++ b/gtksourceview/gtksourcehover.h
@@ -0,0 +1,46 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION)
+# error "Only <gtksourceview/gtksource.h> can be included directly."
+#endif
+
+#include <glib-object.h>
+
+#include "gtksourcetypes.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_HOVER (gtk_source_hover_get_type())
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+G_DECLARE_FINAL_TYPE (GtkSourceHover, gtk_source_hover, GTK_SOURCE, HOVER, GObject)
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_hover_add_provider    (GtkSourceHover         *self,
+                                       GtkSourceHoverProvider *provider);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_hover_remove_provider (GtkSourceHover         *self,
+                                       GtkSourceHoverProvider *provider);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcehoverassistant-private.h b/gtksourceview/gtksourcehoverassistant-private.h
new file mode 100644
index 00000000..7925014a
--- /dev/null
+++ b/gtksourceview/gtksourcehoverassistant-private.h
@@ -0,0 +1,43 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#include "gtksourcetypes.h"
+
+#include "gtksourceassistant-private.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_HOVER_ASSISTANT (gtk_source_hover_assistant_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkSourceHoverAssistant, gtk_source_hover_assistant, GTK_SOURCE, HOVER_ASSISTANT, 
GtkSourceAssistant)
+
+GtkSourceAssistant *_gtk_source_hover_assistant_new     (void);
+void                _gtk_source_hover_assistant_dismiss (GtkSourceHoverAssistant  *self);
+void                _gtk_source_hover_assistant_display (GtkSourceHoverAssistant  *self,
+                                                         GtkSourceHoverProvider  **providers,
+                                                         guint                     n_providers,
+                                                         const GtkTextIter        *begin,
+                                                         const GtkTextIter        *end,
+                                                         const GtkTextIter        *location);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcehoverassistant.c b/gtksourceview/gtksourcehoverassistant.c
new file mode 100644
index 00000000..3d9cbf3e
--- /dev/null
+++ b/gtksourceview/gtksourcehoverassistant.c
@@ -0,0 +1,285 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gtksourceassistant-private.h"
+#include "gtksourcehoverassistant-private.h"
+#include "gtksourcehovercontext-private.h"
+#include "gtksourcehoverdisplay-private.h"
+#include "gtksourceview.h"
+
+#define GRACE_X 20
+#define GRACE_Y 20
+
+struct _GtkSourceHoverAssistant
+{
+       GtkSourceAssistant parent_instance;
+       GtkSourceHoverDisplay *display;
+       GCancellable *cancellable;
+       GdkRectangle hovered_at;
+};
+
+G_DEFINE_TYPE (GtkSourceHoverAssistant, gtk_source_hover_assistant, GTK_SOURCE_TYPE_ASSISTANT)
+
+static void
+gtk_source_hover_assistant_get_target_location (GtkSourceAssistant *assistant,
+                                                GdkRectangle       *rect)
+{
+       *rect = GTK_SOURCE_HOVER_ASSISTANT (assistant)->hovered_at;
+}
+
+static void
+gtk_source_hover_assistant_motion_cb (GtkSourceHoverAssistant  *self,
+                                      double                    x,
+                                      double                    y,
+                                      GtkEventControllerMotion *controller)
+{
+       GdkSurface *assistant_surface;
+       GtkWidget *parent;
+       GtkRoot *root;
+       double tx, ty;
+       int width, height;
+
+       g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self));
+       g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (controller));
+
+       if (gtk_event_controller_motion_contains_pointer (controller))
+       {
+               return;
+       }
+
+       if (!(parent = gtk_widget_get_parent (GTK_WIDGET (self))) ||
+           !(root = gtk_widget_get_root (parent)) ||
+           !(assistant_surface = gtk_native_get_surface (GTK_NATIVE (self))))
+       {
+               return;
+       }
+
+       gtk_widget_translate_coordinates (parent, GTK_WIDGET (root), x, y, &x, &y);
+       x -= gdk_popup_get_position_x (GDK_POPUP (assistant_surface));
+       y -= gdk_popup_get_position_y (GDK_POPUP (assistant_surface));
+
+       gtk_native_get_surface_transform (GTK_NATIVE (root), &tx, &ty);
+       x += tx;
+       y += ty;
+
+       width = gdk_surface_get_width (assistant_surface);
+       height = gdk_surface_get_height (assistant_surface);
+
+       if (x < -GRACE_X ||
+           x > width + GRACE_Y ||
+           y < -GRACE_Y ||
+           y > height + GRACE_Y)
+       {
+               gtk_widget_hide (GTK_WIDGET (self));
+       }
+}
+
+static gboolean
+gtk_source_hover_assistant_scroll_cb (GtkSourceHoverAssistant  *self,
+                                      double                    dx,
+                                      double                    dy,
+                                      GtkEventControllerScroll *controller)
+{
+       g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self));
+       g_assert (GTK_IS_EVENT_CONTROLLER_SCROLL (controller));
+
+       gtk_widget_hide (GTK_WIDGET (self));
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+static void
+gtk_source_hover_assistant_dispose (GObject *object)
+{
+       GtkSourceHoverAssistant *self = (GtkSourceHoverAssistant *)object;
+
+       self->display = NULL;
+
+       g_clear_object (&self->cancellable);
+
+       G_OBJECT_CLASS (gtk_source_hover_assistant_parent_class)->dispose (object);
+}
+
+static void
+gtk_source_hover_assistant_class_init (GtkSourceHoverAssistantClass *klass)
+{
+       GtkSourceAssistantClass *assistant_class = GTK_SOURCE_ASSISTANT_CLASS (klass);
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->dispose = gtk_source_hover_assistant_dispose;
+
+       assistant_class->get_target_location = gtk_source_hover_assistant_get_target_location;
+}
+
+static void
+gtk_source_hover_assistant_init (GtkSourceHoverAssistant *self)
+{
+       GtkEventController *motion;
+       GtkEventController *scroll;
+
+       gtk_widget_add_css_class (GTK_WIDGET (self), "hover-assistant");
+
+       gtk_popover_set_autohide (GTK_POPOVER (self), TRUE);
+
+       self->display = g_object_new (GTK_SOURCE_TYPE_HOVER_DISPLAY, NULL);
+       _gtk_source_assistant_set_child (GTK_SOURCE_ASSISTANT (self), GTK_WIDGET (self->display));
+
+       motion = gtk_event_controller_motion_new ();
+       gtk_event_controller_set_propagation_phase (motion, GTK_PHASE_CAPTURE);
+       g_signal_connect_object (motion,
+                                "motion",
+                                G_CALLBACK (gtk_source_hover_assistant_motion_cb),
+                                self,
+                                G_CONNECT_SWAPPED);
+       gtk_widget_add_controller (GTK_WIDGET (self), motion);
+
+       scroll = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
+       g_signal_connect_object (scroll,
+                                "scroll",
+                                G_CALLBACK (gtk_source_hover_assistant_scroll_cb),
+                                self,
+                                G_CONNECT_SWAPPED);
+       gtk_widget_add_controller (GTK_WIDGET (self), scroll);
+}
+
+GtkSourceAssistant *
+_gtk_source_hover_assistant_new (void)
+{
+       return g_object_new (GTK_SOURCE_TYPE_HOVER_ASSISTANT, NULL);
+}
+
+static void
+gtk_source_hover_assistant_populate_cb (GObject      *object,
+                                        GAsyncResult *result,
+                                        gpointer      user_data)
+{
+       GtkSourceHoverContext *context = (GtkSourceHoverContext *)object;
+       GtkSourceHoverAssistant *self = user_data;
+       GError *error = NULL;
+
+       g_assert (GTK_SOURCE_IS_HOVER_CONTEXT (context));
+       g_assert (G_IS_ASYNC_RESULT (result));
+       g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self));
+
+       if (_gtk_source_hover_context_populate_finish (context, result, &error))
+       {
+               gtk_widget_show (GTK_WIDGET (self));
+       }
+
+       g_clear_object (&self);
+       g_clear_error (&error);
+}
+
+void
+_gtk_source_hover_assistant_display (GtkSourceHoverAssistant  *self,
+                                     GtkSourceHoverProvider  **providers,
+                                     guint                     n_providers,
+                                     const GtkTextIter        *begin,
+                                     const GtkTextIter        *end,
+                                     const GtkTextIter        *location)
+{
+       GtkSourceHoverContext *context;
+       GtkSourceView *view;
+       GdkRectangle begin_rect;
+       GdkRectangle end_rect;
+       GdkRectangle location_rect;
+
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_ASSISTANT (self));
+       g_return_if_fail (n_providers == 0 || providers != NULL);
+       g_return_if_fail (begin != NULL);
+       g_return_if_fail (end != NULL);
+       g_return_if_fail (location != NULL);
+
+       if (n_providers == 0)
+       {
+               if (gtk_widget_get_visible (GTK_WIDGET (self)))
+               {
+                       gtk_widget_hide (GTK_WIDGET (self));
+               }
+
+               return;
+       }
+
+       if (self->cancellable != NULL)
+       {
+               g_cancellable_cancel (self->cancellable);
+               g_clear_object (&self->cancellable);
+       }
+
+       view = GTK_SOURCE_VIEW (gtk_widget_get_parent (GTK_WIDGET (self)));
+
+       gtk_text_view_get_iter_location (GTK_TEXT_VIEW (view), begin, &begin_rect);
+       gtk_text_view_get_iter_location (GTK_TEXT_VIEW (view), end, &end_rect);
+       gtk_text_view_get_iter_location (GTK_TEXT_VIEW (view), location, &location_rect);
+
+       gdk_rectangle_union (&begin_rect, &end_rect, &location_rect);
+
+       gtk_text_view_buffer_to_window_coords (GTK_TEXT_VIEW (view),
+                                              GTK_TEXT_WINDOW_WIDGET,
+                                              location_rect.x,
+                                              location_rect.y,
+                                              &location_rect.x,
+                                              &location_rect.y);
+
+       if (gtk_text_iter_equal (begin, end) &&
+           gtk_text_iter_starts_line (begin))
+       {
+               location_rect.width = 1;
+               gtk_popover_set_position (GTK_POPOVER (self), GTK_POS_RIGHT);
+       }
+       else
+       {
+               gtk_popover_set_position (GTK_POPOVER (self), GTK_POS_TOP);
+       }
+
+       self->hovered_at = location_rect;
+
+       context = _gtk_source_hover_context_new (view, begin, end, location);
+
+       for (guint i = 0; i < n_providers; i++)
+       {
+               _gtk_source_hover_context_add_provider (context, providers[i]);
+       }
+
+       self->cancellable = g_cancellable_new ();
+
+       _gtk_source_hover_display_clear (self->display);
+
+       _gtk_source_hover_context_populate_async (context,
+                                                 self->display,
+                                                 self->cancellable,
+                                                 gtk_source_hover_assistant_populate_cb,
+                                                 g_object_ref (self));
+
+       g_object_unref (context);
+}
+
+void
+_gtk_source_hover_assistant_dismiss (GtkSourceHoverAssistant *self)
+{
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_ASSISTANT (self));
+
+       g_cancellable_cancel (self->cancellable);
+       gtk_widget_hide (GTK_WIDGET (self));
+       _gtk_source_hover_display_clear (self->display);
+}
diff --git a/gtksourceview/gtksourcehovercontext-private.h b/gtksourceview/gtksourcehovercontext-private.h
new file mode 100644
index 00000000..3e19b69c
--- /dev/null
+++ b/gtksourceview/gtksourcehovercontext-private.h
@@ -0,0 +1,44 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#include "gtksourcehovercontext.h"
+
+G_BEGIN_DECLS
+
+GtkSourceHoverContext *_gtk_source_hover_context_new            (GtkSourceView           *view,
+                                                                 const GtkTextIter       *begin,
+                                                                 const GtkTextIter       *end,
+                                                                 const GtkTextIter       *location);
+void                  _gtk_source_hover_context_add_provider    (GtkSourceHoverContext   *self,
+                                                                 GtkSourceHoverProvider  *provider);
+void                  _gtk_source_hover_context_populate_async  (GtkSourceHoverContext   *self,
+                                                                 GtkSourceHoverDisplay   *display,
+                                                                 GCancellable            *cancellable,
+                                                                 GAsyncReadyCallback      callback,
+                                                                 gpointer                 user_data);
+gboolean              _gtk_source_hover_context_populate_finish (GtkSourceHoverContext   *self,
+                                                                 GAsyncResult            *result,
+                                                                 GError                 **error);
+
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcehovercontext.c b/gtksourceview/gtksourcehovercontext.c
new file mode 100644
index 00000000..a122b26e
--- /dev/null
+++ b/gtksourceview/gtksourcehovercontext.c
@@ -0,0 +1,351 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gtksourcebuffer.h"
+#include "gtksourcehovercontext-private.h"
+#include "gtksourcehoverdisplay.h"
+#include "gtksourcehoverprovider.h"
+#include "gtksourceview.h"
+
+/**
+ * SECTION:hovercontext
+ * @Title: GtkSourceHoverContext
+ * @Short_description: Context for populating #GtkSourceHoverDisplay contents
+ * @See_also: #GtkSourceHover, #GtkSourceHoverProvider, #GtkSourceHoverDisplay
+ *
+ * #GtkSourceHoverContext contains information about the request to populate
+ * contents for a #GtkSourceHoverDisplay.
+ *
+ * It can be used to retrieve the #GtkSourceView, #GtkSourceBuffer, and
+ * #GtkTextIter for the regions of text which are being displayed.
+ *
+ * Use gtk_source_hover_context_get_bounds() to get the word that was
+ * requested. gtk_source_hover_contents_get_iter() will get you the location
+ * of the pointer when the request was made.
+ *
+ * Since: 5.0
+ */
+
+struct _GtkSourceHoverContext
+{
+       GObject          parent_instance;
+
+       GtkSourceView   *view;
+       GtkSourceBuffer *buffer;
+
+       GPtrArray       *providers;
+
+       GtkTextMark     *begin;
+       GtkTextMark     *end;
+       GtkTextMark     *location;
+};
+
+G_DEFINE_TYPE (GtkSourceHoverContext, gtk_source_hover_context, G_TYPE_OBJECT)
+
+typedef struct
+{
+       guint n_active;
+} Populate;
+
+static void
+gtk_source_hover_context_dispose (GObject *object)
+{
+       GtkSourceHoverContext *self = (GtkSourceHoverContext *)object;
+
+       if (self->buffer != NULL)
+       {
+               if (self->begin != NULL)
+                       gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (self->buffer), self->begin);
+
+               if (self->end != NULL)
+                       gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (self->buffer), self->end);
+
+               if (self->location != NULL)
+                       gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (self->buffer), self->location);
+       }
+
+       g_clear_object (&self->begin);
+       g_clear_object (&self->end);
+       g_clear_object (&self->location);
+
+       if (self->providers->len > 0)
+       {
+               g_ptr_array_remove_range (self->providers, 0, self->providers->len);
+       }
+
+       g_clear_weak_pointer (&self->buffer);
+       g_clear_weak_pointer (&self->view);
+
+       G_OBJECT_CLASS (gtk_source_hover_context_parent_class)->dispose (object);
+}
+
+static void
+gtk_source_hover_context_finalize (GObject *object)
+{
+       GtkSourceHoverContext *self = (GtkSourceHoverContext *)object;
+
+       g_clear_pointer (&self->providers, g_ptr_array_unref);
+
+       G_OBJECT_CLASS (gtk_source_hover_context_parent_class)->finalize (object);
+}
+
+static void
+gtk_source_hover_context_class_init (GtkSourceHoverContextClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->dispose = gtk_source_hover_context_dispose;
+       object_class->finalize = gtk_source_hover_context_finalize;
+}
+
+static void
+gtk_source_hover_context_init (GtkSourceHoverContext *self)
+{
+       self->providers = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+void
+_gtk_source_hover_context_add_provider (GtkSourceHoverContext  *self,
+                                        GtkSourceHoverProvider *provider)
+{
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (self));
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_PROVIDER (provider));
+
+       for (guint i = 0; i < self->providers->len; i++)
+       {
+               if (g_ptr_array_index (self->providers, i) == (gpointer)provider)
+               {
+                       return;
+               }
+       }
+
+       g_ptr_array_add (self->providers, g_object_ref (provider));
+}
+
+/**
+ * gtk_source_hover_context_get_view:
+ *
+ * Returns: (transfer none): the #GtkSourceView that owns the context
+ */
+GtkSourceView *
+gtk_source_hover_context_get_view (GtkSourceHoverContext *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (self), NULL);
+
+       return self->view;
+}
+
+/**
+ * gtk_source_hover_context_get_buffer:
+ *
+ * A convenience function to get the buffer.
+ *
+ * Returns: (transfer none): The #GtkSourceBuffer for the view
+ */
+GtkSourceBuffer *
+gtk_source_hover_context_get_buffer (GtkSourceHoverContext *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (self), NULL);
+       g_return_val_if_fail (self->view != NULL, NULL);
+
+       return GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->view)));
+}
+
+static GtkTextMark *
+create_mark (GtkSourceHoverContext *self,
+             const GtkTextIter     *iter,
+             gboolean               left_gravity)
+{
+       GtkTextMark *mark;
+       GtkTextBuffer *buffer;
+
+       g_assert (GTK_SOURCE_IS_HOVER_CONTEXT (self));
+
+       buffer = GTK_TEXT_BUFFER (self->buffer);
+       mark = gtk_text_buffer_create_mark (buffer, NULL, iter, left_gravity);
+
+       return g_object_ref (mark);
+}
+
+GtkSourceHoverContext *
+_gtk_source_hover_context_new (GtkSourceView     *view,
+                               const GtkTextIter *begin,
+                               const GtkTextIter *end,
+                               const GtkTextIter *location)
+{
+       GtkSourceHoverContext *self;
+       GtkSourceBuffer *buffer;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), NULL);
+       g_return_val_if_fail (begin != NULL, NULL);
+       g_return_val_if_fail (end != NULL, NULL);
+       g_return_val_if_fail (location != NULL, NULL);
+
+       buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+       self = g_object_new (GTK_SOURCE_TYPE_HOVER_CONTEXT, NULL);
+
+       g_set_weak_pointer (&self->view, view);
+       g_set_weak_pointer (&self->buffer, buffer);
+
+       self->begin = create_mark (self, begin, TRUE);
+       self->end = create_mark (self, end, FALSE);
+       self->location = create_mark (self, location, FALSE);
+
+       return self;
+}
+
+static void
+gtk_source_hover_context_populate_cb (GObject      *object,
+                                      GAsyncResult *result,
+                                      gpointer      user_data)
+{
+       GtkSourceHoverProvider *provider = (GtkSourceHoverProvider *)object;
+       GTask *task = user_data;
+       Populate *state;
+       GError *error = NULL;
+
+       g_assert (GTK_SOURCE_IS_HOVER_PROVIDER (provider));
+       g_assert (G_IS_ASYNC_RESULT (result));
+       g_assert (G_IS_TASK (task));
+
+       state = g_task_get_task_data (task);
+
+       if (!gtk_source_hover_provider_populate_finish (provider, result, &error))
+       {
+               if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+                   !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
+               {
+                       g_debug ("%s population failed", error->message);
+               }
+
+               g_clear_error (&error);
+       }
+
+       if (--state->n_active == 0)
+       {
+               g_task_return_boolean (task, TRUE);
+       }
+
+       g_object_unref (task);
+}
+
+void
+_gtk_source_hover_context_populate_async (GtkSourceHoverContext *self,
+                                          GtkSourceHoverDisplay *display,
+                                          GCancellable          *cancellable,
+                                          GAsyncReadyCallback    callback,
+                                          gpointer               user_data)
+{
+       Populate *state;
+       GTask *task;
+
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (self));
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_DISPLAY (display));
+       g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+       state = g_new0 (Populate, 1);
+       state->n_active = self->providers->len;
+
+       task = g_task_new (self, cancellable, callback, user_data);
+       g_task_set_source_tag (task, _gtk_source_hover_context_populate_async);
+       g_task_set_task_data (task, state, g_free);
+
+       if (self->view == NULL || self->buffer == NULL)
+       {
+               g_task_return_new_error (task,
+                                        G_IO_ERROR,
+                                        G_IO_ERROR_CANCELLED,
+                                        "Cannot populate, view destroyed");
+       }
+       else if (g_task_return_error_if_cancelled (task))
+       {
+               /* Do nothing */
+       }
+       else if (self->providers->len  == 0)
+       {
+               g_task_return_boolean (task, TRUE);
+       }
+       else
+       {
+               for (guint i = 0; i < self->providers->len; i++)
+               {
+                       GtkSourceHoverProvider *provider = g_ptr_array_index (self->providers, i);
+
+                       g_assert (GTK_SOURCE_IS_HOVER_PROVIDER (provider));
+
+                       gtk_source_hover_provider_populate_async (provider,
+                                                                 self,
+                                                                 display,
+                                                                 cancellable,
+                                                                 gtk_source_hover_context_populate_cb,
+                                                                 g_object_ref (task));
+               }
+       }
+
+       g_object_unref (task);
+}
+
+gboolean
+_gtk_source_hover_context_populate_finish (GtkSourceHoverContext  *self,
+                                           GAsyncResult           *result,
+                                           GError                **error)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (self), FALSE);
+       g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+       return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+gboolean
+gtk_source_hover_context_get_iter (GtkSourceHoverContext *self,
+                                   GtkTextIter           *iter)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (self), FALSE);
+       g_return_val_if_fail (iter != NULL, FALSE);
+
+       if (self->buffer == NULL)
+               return FALSE;
+
+       gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self->buffer), iter, self->location);
+
+       return TRUE;
+}
+
+gboolean
+gtk_source_hover_context_get_bounds (GtkSourceHoverContext *self,
+                                     GtkTextIter           *begin,
+                                     GtkTextIter           *end)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (self), FALSE);
+
+       if (self->buffer == NULL)
+               return FALSE;
+
+       if (begin != NULL)
+               gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self->buffer), begin, self->begin);
+
+       if (end != NULL)
+               gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self->buffer), end, self->end);
+
+       return TRUE;
+}
diff --git a/gtksourceview/gtksourcehovercontext.h b/gtksourceview/gtksourcehovercontext.h
new file mode 100644
index 00000000..aee8f535
--- /dev/null
+++ b/gtksourceview/gtksourcehovercontext.h
@@ -0,0 +1,51 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION)
+# error "Only <gtksourceview/gtksource.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+
+#include "gtksourcetypes.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_HOVER_CONTEXT (gtk_source_hover_context_get_type())
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+G_DECLARE_FINAL_TYPE (GtkSourceHoverContext, gtk_source_hover_context, GTK_SOURCE, HOVER_CONTEXT, GObject)
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceView   *gtk_source_hover_context_get_view   (GtkSourceHoverContext *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceBuffer *gtk_source_hover_context_get_buffer (GtkSourceHoverContext *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+gboolean         gtk_source_hover_context_get_iter   (GtkSourceHoverContext *self,
+                                                      GtkTextIter           *iter);
+GTK_SOURCE_AVAILABLE_IN_5_0
+gboolean         gtk_source_hover_context_get_bounds (GtkSourceHoverContext *self,
+                                                      GtkTextIter           *begin,
+                                                      GtkTextIter           *end);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcehoverdisplay-private.h b/gtksourceview/gtksourcehoverdisplay-private.h
new file mode 100644
index 00000000..3ee9fd08
--- /dev/null
+++ b/gtksourceview/gtksourcehoverdisplay-private.h
@@ -0,0 +1,30 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#include "gtksourcehoverdisplay.h"
+
+G_BEGIN_DECLS
+
+void _gtk_source_hover_display_clear (GtkSourceHoverDisplay *self);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcehoverdisplay.c b/gtksourceview/gtksourcehoverdisplay.c
new file mode 100644
index 00000000..a68f40b5
--- /dev/null
+++ b/gtksourceview/gtksourcehoverdisplay.c
@@ -0,0 +1,142 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gtksourcehoverdisplay-private.h"
+
+/**
+ * SECTION:hoverdisplay
+ * @Title: GtkSourceHoverDisplay
+ * @Short_description: display for interactive tooltips
+ * @See_also: #GtkSourceHoverProvider
+ *
+ * #GtkSourceHoverDisplay is a #GtkWidget that may be populated with widgets
+ * to be displayed to the user in interactive tooltips. The children widgets
+ * are packed vertically using a #GtkBox.
+ *
+ * Implement the #GtkSourceHoverProvider interface to be notified of when
+ * to populate a #GtkSourceHoverDisplay on behalf of the user.
+ *
+ * Since: 5.0
+ */
+
+struct _GtkSourceHoverDisplay
+{
+       GtkWidget parent_instance;
+
+       GtkBox *vbox;
+};
+
+G_DEFINE_TYPE (GtkSourceHoverDisplay, gtk_source_hover_display, GTK_TYPE_WIDGET)
+
+static void
+gtk_source_hover_display_dispose (GObject *object)
+{
+       GtkSourceHoverDisplay *self = (GtkSourceHoverDisplay *)object;
+
+       g_clear_pointer ((GtkWidget **)&self->vbox, gtk_widget_unparent);
+
+       G_OBJECT_CLASS (gtk_source_hover_display_parent_class)->dispose (object);
+}
+
+static void
+gtk_source_hover_display_class_init (GtkSourceHoverDisplayClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+       object_class->dispose = gtk_source_hover_display_dispose;
+
+       gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+}
+
+static void
+gtk_source_hover_display_init (GtkSourceHoverDisplay *self)
+{
+       self->vbox = g_object_new (GTK_TYPE_BOX,
+                                  "orientation", GTK_ORIENTATION_VERTICAL,
+                                  NULL);
+       gtk_widget_set_parent (GTK_WIDGET (self->vbox), GTK_WIDGET (self));
+}
+
+void
+gtk_source_hover_display_append (GtkSourceHoverDisplay *self,
+                                 GtkWidget             *child)
+{
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_DISPLAY (self));
+       g_return_if_fail (GTK_IS_WIDGET (child));
+
+       gtk_box_append (self->vbox, child);
+}
+
+void
+gtk_source_hover_display_prepend (GtkSourceHoverDisplay *self,
+                                  GtkWidget             *child)
+{
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_DISPLAY (self));
+       g_return_if_fail (GTK_IS_WIDGET (child));
+
+       gtk_box_prepend (self->vbox, child);
+}
+
+void
+gtk_source_hover_display_insert_after (GtkSourceHoverDisplay *self,
+                                       GtkWidget             *child,
+                                       GtkWidget             *sibling)
+{
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_DISPLAY (self));
+       g_return_if_fail (GTK_IS_WIDGET (child));
+       g_return_if_fail (!sibling || GTK_IS_WIDGET (sibling));
+
+       if (sibling == NULL)
+       {
+               gtk_source_hover_display_append (self, child);
+       }
+       else
+       {
+               gtk_box_insert_child_after (self->vbox, child, sibling);
+       }
+}
+
+void
+gtk_source_hover_display_remove (GtkSourceHoverDisplay *self,
+                                 GtkWidget             *child)
+{
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_DISPLAY (self));
+       g_return_if_fail (GTK_IS_WIDGET (child));
+       g_return_if_fail (gtk_widget_get_parent (child) == (GtkWidget *)self->vbox);
+
+       gtk_box_remove (self->vbox, child);
+}
+
+void
+_gtk_source_hover_display_clear (GtkSourceHoverDisplay *self)
+{
+       GtkWidget *child;
+
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_DISPLAY (self));
+
+       while ((child = gtk_widget_get_first_child (GTK_WIDGET (self->vbox))))
+       {
+               gtk_box_remove (self->vbox, child);
+       }
+}
diff --git a/gtksourceview/gtksourcehoverdisplay.h b/gtksourceview/gtksourcehoverdisplay.h
new file mode 100644
index 00000000..6a06b761
--- /dev/null
+++ b/gtksourceview/gtksourcehoverdisplay.h
@@ -0,0 +1,53 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION)
+# error "Only <gtksourceview/gtksource.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+
+#include "gtksourcetypes.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_HOVER_DISPLAY (gtk_source_hover_display_get_type())
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+G_DECLARE_FINAL_TYPE (GtkSourceHoverDisplay, gtk_source_hover_display, GTK_SOURCE, HOVER_DISPLAY, GtkWidget)
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_hover_display_append       (GtkSourceHoverDisplay *self,
+                                            GtkWidget             *child);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_hover_display_prepend      (GtkSourceHoverDisplay *self,
+                                            GtkWidget             *child);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_hover_display_insert_after (GtkSourceHoverDisplay *self,
+                                            GtkWidget             *child,
+                                            GtkWidget             *sibling);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_hover_display_remove       (GtkSourceHoverDisplay *self,
+                                            GtkWidget             *child);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcehoverprovider.c b/gtksourceview/gtksourcehoverprovider.c
new file mode 100644
index 00000000..40ce1ef2
--- /dev/null
+++ b/gtksourceview/gtksourcehoverprovider.c
@@ -0,0 +1,120 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gtksourcehovercontext.h"
+#include "gtksourcehoverdisplay.h"
+#include "gtksourcehoverprovider.h"
+
+/**
+ * SETION:hoverprovider
+ * @Title: GtkSourceHoverProvider
+ * @Short_description: interface to populate interactive tooltips
+ * @See_also: #GtkSourceHover, #GtkSourceHoverDisplay, #GtkSourceHoverContext
+ *
+ * #GtkSourceHoverProvider is an interface that should be implemented to extend
+ * the contents of a #GtkSourceHoverDisplay. This is typical in editors that
+ * interact external tooling such as those utilizing Language Server Protocol.
+ *
+ * If you can populate the #GtkSourceHoverDisplay synchronously, use
+ * #GtkSourceHoverProvider.populate. Otherwise, interface implementations that
+ * may take additional time should use #GtkSourceHoverProvider.populate_async
+ * to avoid blocking the main loop.
+ *
+ * Since: 5.0
+ */
+
+G_DEFINE_INTERFACE (GtkSourceHoverProvider, gtk_source_hover_provider, G_TYPE_OBJECT)
+
+static gboolean
+gtk_source_hover_provider_real_populate (GtkSourceHoverProvider  *provider,
+                                         GtkSourceHoverContext   *context,
+                                         GtkSourceHoverDisplay   *display,
+                                         GError                 **error)
+{
+       return TRUE;
+}
+
+static void
+gtk_source_hover_provider_real_populate_async (GtkSourceHoverProvider *provider,
+                                               GtkSourceHoverContext  *context,
+                                               GtkSourceHoverDisplay  *display,
+                                               GCancellable           *cancellable,
+                                               GAsyncReadyCallback     callback,
+                                               gpointer                user_data)
+{
+       GError *error = NULL;
+       GTask *task;
+
+       task = g_task_new (provider, cancellable, callback, user_data);
+       g_task_set_source_tag (task, gtk_source_hover_provider_real_populate_async);
+
+       if (!GTK_SOURCE_HOVER_PROVIDER_GET_IFACE (provider)->populate (provider, context, display, &error))
+               g_task_return_error (task, g_steal_pointer (&error));
+       else
+               g_task_return_boolean (task, TRUE);
+
+       g_object_unref (task);
+}
+
+static gboolean
+gtk_source_hover_provider_real_populate_finish (GtkSourceHoverProvider  *self,
+                                                GAsyncResult            *result,
+                                                GError                 **error)
+{
+       return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+gtk_source_hover_provider_default_init (GtkSourceHoverProviderInterface *iface)
+{
+       iface->populate_async = gtk_source_hover_provider_real_populate_async;
+       iface->populate_finish = gtk_source_hover_provider_real_populate_finish;
+       iface->populate = gtk_source_hover_provider_real_populate;
+}
+
+void
+gtk_source_hover_provider_populate_async (GtkSourceHoverProvider *provider,
+                                          GtkSourceHoverContext  *context,
+                                          GtkSourceHoverDisplay  *display,
+                                          GCancellable           *cancellable,
+                                          GAsyncReadyCallback     callback,
+                                          gpointer                user_data)
+{
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_PROVIDER (provider));
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (context));
+       g_return_if_fail (GTK_SOURCE_IS_HOVER_DISPLAY (display));
+       g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+       GTK_SOURCE_HOVER_PROVIDER_GET_IFACE (provider)->populate_async (provider, context, display, 
cancellable, callback, user_data);
+}
+
+gboolean
+gtk_source_hover_provider_populate_finish (GtkSourceHoverProvider  *provider,
+                                           GAsyncResult            *result,
+                                           GError                 **error)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_HOVER_PROVIDER (provider), FALSE);
+       g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+       return GTK_SOURCE_HOVER_PROVIDER_GET_IFACE (provider)->populate_finish (provider, result, error);
+}
diff --git a/gtksourceview/gtksourcehoverprovider.h b/gtksourceview/gtksourcehoverprovider.h
new file mode 100644
index 00000000..c662c0a2
--- /dev/null
+++ b/gtksourceview/gtksourcehoverprovider.h
@@ -0,0 +1,70 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION)
+# error "Only <gtksourceview/gtksource.h> can be included directly."
+#endif
+
+#include <gio/gio.h>
+
+#include "gtksourcetypes.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_HOVER_PROVIDER (gtk_source_hover_provider_get_type())
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+G_DECLARE_INTERFACE (GtkSourceHoverProvider, gtk_source_hover_provider, GTK_SOURCE, HOVER_PROVIDER, GObject)
+
+struct _GtkSourceHoverProviderInterface
+{
+       GTypeInterface parent_iface;
+
+       gboolean (*populate)        (GtkSourceHoverProvider  *self,
+                                    GtkSourceHoverContext   *context,
+                                    GtkSourceHoverDisplay   *display,
+                                    GError                 **error);
+       void     (*populate_async)  (GtkSourceHoverProvider  *self,
+                                    GtkSourceHoverContext   *context,
+                                    GtkSourceHoverDisplay   *display,
+                                    GCancellable            *cancellable,
+                                    GAsyncReadyCallback      callback,
+                                    gpointer                 user_data);
+       gboolean (*populate_finish) (GtkSourceHoverProvider  *self,
+                                    GAsyncResult            *result,
+                                    GError                 **error);
+};
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+void     gtk_source_hover_provider_populate_async  (GtkSourceHoverProvider  *self,
+                                                    GtkSourceHoverContext   *context,
+                                                    GtkSourceHoverDisplay   *display,
+                                                    GCancellable            *cancellable,
+                                                    GAsyncReadyCallback      callback,
+                                                    gpointer                 user_data);
+GTK_SOURCE_AVAILABLE_IN_5_0
+gboolean gtk_source_hover_provider_populate_finish (GtkSourceHoverProvider  *self,
+                                                    GAsyncResult                 *result,
+                                                    GError                      **error);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcetypes.h b/gtksourceview/gtksourcetypes.h
index 85156f61..6f1530cc 100644
--- a/gtksourceview/gtksourcetypes.h
+++ b/gtksourceview/gtksourcetypes.h
@@ -50,6 +50,10 @@ typedef struct _GtkSourceGutterLines               GtkSourceGutterLines;
 typedef struct _GtkSourceGutterRenderer            GtkSourceGutterRenderer;
 typedef struct _GtkSourceGutterRendererPixbuf      GtkSourceGutterRendererPixbuf;
 typedef struct _GtkSourceGutterRendererText        GtkSourceGutterRendererText;
+typedef struct _GtkSourceHover                     GtkSourceHover;
+typedef struct _GtkSourceHoverContext              GtkSourceHoverContext;
+typedef struct _GtkSourceHoverDisplay              GtkSourceHoverDisplay;
+typedef struct _GtkSourceHoverProvider             GtkSourceHoverProvider;
 typedef struct _GtkSourceLanguage                  GtkSourceLanguage;
 typedef struct _GtkSourceLanguageManager           GtkSourceLanguageManager;
 typedef struct _GtkSourceMap                       GtkSourceMap;
diff --git a/gtksourceview/gtksourceview.c b/gtksourceview/gtksourceview.c
index e4292dd2..e2abe64b 100644
--- a/gtksourceview/gtksourceview.c
+++ b/gtksourceview/gtksourceview.c
@@ -39,6 +39,7 @@
 #include "gtksourcegutter-private.h"
 #include "gtksourcegutterrendererlines-private.h"
 #include "gtksourcegutterrenderermarks-private.h"
+#include "gtksourcehover-private.h"
 #include "gtksource-enumtypes.h"
 #include "gtksourcemark.h"
 #include "gtksourcemarkattributes.h"
@@ -189,6 +190,7 @@ typedef struct
        GdkRGBA right_margin_overlay_color;
 
        GtkSourceCompletion *completion;
+       GtkSourceHover *hover;
 
        guint right_margin_pos;
        gint cached_right_margin_pos;
@@ -1455,6 +1457,12 @@ gtk_source_view_dispose (GObject *object)
                g_clear_object (&priv->completion);
        }
 
+       if (priv->hover != NULL)
+       {
+               g_object_run_dispose (G_OBJECT (priv->hover));
+               g_clear_object (&priv->hover);
+       }
+
        g_clear_object (&priv->style_scheme);
        g_clear_object (&priv->space_drawer);
 
@@ -4805,6 +4813,31 @@ gtk_source_view_get_completion (GtkSourceView *view)
        return priv->completion;
 }
 
+/**
+ * gtk_source_view_get_hover:
+ * @view: a #GtkSourceView.
+ *
+ * Gets the #GtkSourceHover associated with @view. The returned object is
+ * guaranteed to be the same for the lifetime of @view. Each #GtkSourceView
+ * object has a different #GtkSourceHover.
+ *
+ * Returns: (transfer none): a #GtkSourceHover associated with @view.
+ */
+GtkSourceHover *
+gtk_source_view_get_hover (GtkSourceView *view)
+{
+       GtkSourceViewPrivate *priv = gtk_source_view_get_instance_private (view);
+
+       g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), NULL);
+
+       if (priv->hover == NULL)
+       {
+               priv->hover = _gtk_source_hover_new (view);
+       }
+
+       return priv->hover;
+}
+
 /**
  * gtk_source_view_get_gutter:
  * @view: a #GtkSourceView.
diff --git a/gtksourceview/gtksourceview.h b/gtksourceview/gtksourceview.h
index 4b448722..ab154a39 100644
--- a/gtksourceview/gtksourceview.h
+++ b/gtksourceview/gtksourceview.h
@@ -195,6 +195,8 @@ guint                           gtk_source_view_get_visual_column
                                                                                    const GtkTextIter         
     *iter);
 GTK_SOURCE_AVAILABLE_IN_ALL
 GtkSourceCompletion            *gtk_source_view_get_completion                    (GtkSourceView             
     *view);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceHover                 *gtk_source_view_get_hover                         (GtkSourceView             
     *view);
 GTK_SOURCE_AVAILABLE_IN_ALL
 GtkSourceGutter                *gtk_source_view_get_gutter                        (GtkSourceView             
     *view,
                                                                                    GtkTextWindowType         
      window_type);
diff --git a/gtksourceview/meson.build b/gtksourceview/meson.build
index 4bc7041f..4abec0c0 100644
--- a/gtksourceview/meson.build
+++ b/gtksourceview/meson.build
@@ -21,6 +21,10 @@ core_public_h = files([
   'gtksourcegutterrenderer.h',
   'gtksourcegutterrendererpixbuf.h',
   'gtksourcegutterrenderertext.h',
+  'gtksourcehover.h',
+  'gtksourcehovercontext.h',
+  'gtksourcehoverprovider.h',
+  'gtksourcehoverdisplay.h',
   'gtksourceinit.h',
   'gtksourcelanguage.h',
   'gtksourcelanguagemanager.h',
@@ -64,6 +68,10 @@ core_public_c = files([
   'gtksourcegutterrenderer.c',
   'gtksourcegutterrendererpixbuf.c',
   'gtksourcegutterrenderertext.c',
+  'gtksourcehover.c',
+  'gtksourcehovercontext.c',
+  'gtksourcehoverdisplay.c',
+  'gtksourcehoverprovider.c',
   'gtksourceinit.c',
   'gtksourcelanguage.c',
   'gtksourcelanguagemanager.c',
@@ -107,6 +115,7 @@ core_private_c = files([
   'gtksourceengine.c',
   'gtksourcegutterrendererlines.c',
   'gtksourcegutterrenderermarks.c',
+  'gtksourcehoverassistant.c',
   'gtksourceinformative.c',
   'gtksourceiter.c',
   'gtksourcelanguage-parser-2.c',
diff --git a/tests/test-widget.c b/tests/test-widget.c
index 2ee4521f..ec00016c 100644
--- a/tests/test-widget.c
+++ b/tests/test-widget.c
@@ -22,7 +22,11 @@
 #include <string.h>
 #include <gtksourceview/gtksource.h>
 
+#define TEST_TYPE_WIDGET (test_widget_get_type())
+#define TEST_TYPE_HOVER_PROVIDER (test_hover_provider_get_type())
+
 G_DECLARE_FINAL_TYPE (TestWidget, test_widget, TEST, WIDGET, GtkGrid)
+G_DECLARE_FINAL_TYPE (TestHoverProvider, test_hover_provider, TEST, HOVER_PROVIDER, GObject)
 
 struct _TestWidget
 {
@@ -44,9 +48,16 @@ struct _TestWidget
        GtkWidget *top;
 };
 
-GType test_widget_get_type (void);
+struct _TestHoverProvider
+{
+       GObject parent_instance;
+};
+
+static void hover_provider_iface_init (GtkSourceHoverProviderInterface *iface);
 
 G_DEFINE_TYPE (TestWidget, test_widget, GTK_TYPE_GRID)
+G_DEFINE_TYPE_WITH_CODE (TestHoverProvider, test_hover_provider, G_TYPE_OBJECT,
+                        G_IMPLEMENT_INTERFACE (GTK_SOURCE_TYPE_HOVER_PROVIDER, hover_provider_iface_init))
 
 #define MARK_TYPE_1      "one"
 #define MARK_TYPE_2      "two"
@@ -946,6 +957,31 @@ enable_snippets_toggled_cb (TestWidget     *self,
        gtk_source_view_set_enable_snippets (self->view, enabled);
 }
 
+static GtkSourceHoverProvider *
+create_hover_provider (void)
+{
+       return g_object_new (TEST_TYPE_HOVER_PROVIDER, NULL);
+}
+
+static void
+enable_hover_toggled_cb (TestWidget     *self,
+                         GtkCheckButton *button)
+{
+       static GtkSourceHoverProvider *test_hover_provider;
+       GtkSourceHover *hover = gtk_source_view_get_hover (self->view);
+       gboolean enabled = gtk_check_button_get_active (button);
+
+       if (test_hover_provider == NULL)
+       {
+               test_hover_provider = create_hover_provider ();
+       }
+
+       if (enabled)
+               gtk_source_hover_add_provider (hover, test_hover_provider);
+       else
+               gtk_source_hover_remove_provider (hover, test_hover_provider);
+}
+
 static void
 test_widget_dispose (GObject *object)
 {
@@ -985,6 +1021,7 @@ test_widget_class_init (TestWidgetClass *klass)
        gtk_widget_class_bind_template_callback (widget_class, forward_string_clicked_cb);
        gtk_widget_class_bind_template_callback (widget_class, smart_home_end_changed_cb);
        gtk_widget_class_bind_template_callback (widget_class, enable_snippets_toggled_cb);
+       gtk_widget_class_bind_template_callback (widget_class, enable_hover_toggled_cb);
 
        gtk_widget_class_bind_template_child (widget_class, TestWidget, view);
        gtk_widget_class_bind_template_child (widget_class, TestWidget, map);
@@ -1120,6 +1157,41 @@ test_widget_new (void)
        return g_object_new (test_widget_get_type (), NULL);
 }
 
+static gboolean
+test_hover_provider_populate (GtkSourceHoverProvider  *provider,
+                              GtkSourceHoverContext   *context,
+                              GtkSourceHoverDisplay   *display,
+                              GError                 **error)
+{
+       GtkTextIter begin, end;
+
+       if (gtk_source_hover_context_get_bounds (context, &begin, &end))
+       {
+               gchar *text = gtk_text_iter_get_slice (&begin, &end);
+               GtkWidget *label = gtk_label_new (text);
+               gtk_source_hover_display_append (display, label);
+               g_free (text);
+       }
+
+       return TRUE;
+}
+
+static void
+hover_provider_iface_init (GtkSourceHoverProviderInterface *iface)
+{
+       iface->populate = test_hover_provider_populate;
+}
+
+static void
+test_hover_provider_class_init (TestHoverProviderClass *klass)
+{
+}
+
+static void
+test_hover_provider_init (TestHoverProvider *self)
+{
+}
+
 static void
 setup_search_paths (void)
 {
@@ -1140,7 +1212,6 @@ setup_search_paths (void)
        gtk_source_language_manager_set_search_path (languages, langs_path);
 }
 
-
 int
 main (int argc, char *argv[])
 {
diff --git a/tests/test-widget.ui b/tests/test-widget.ui
index 1d5f7460..ca6d7d10 100644
--- a/tests/test-widget.ui
+++ b/tests/test-widget.ui
@@ -228,6 +228,17 @@
                 </layout>
               </object>
             </child>
+            <child>
+              <object class="GtkCheckButton" id="enable_hover">
+                <property name="label">Enable hoverers</property>
+                <property name="can-focus">1</property>
+                <signal name="toggled" handler="enable_hover_toggled_cb" object="TestWidget" swapped="yes"/>
+                <layout>
+                  <property name="row">14</property>
+                  <property name="column">0</property>
+                </layout>
+              </object>
+            </child>
             <child>
               <object class="GtkGrid" id="grid10">
                 <layout>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]