[gtksourceview/wip/chergert/gsv-gtk4: 194/259] completion: new completion engine
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtksourceview/wip/chergert/gsv-gtk4: 194/259] completion: new completion engine
- Date: Mon, 21 Sep 2020 23:27:47 +0000 (UTC)
commit 3644449c16935cbb5f3afd299f5764166832155f
Author: Christian Hergert <chergert redhat com>
Date: Sat Aug 29 23:10:31 2020 -0700
completion: new completion engine
This starts the landing of a new completion engine for GtkSourceView.
.../completion-providers/words/meson.build | 12 +-
gtksourceview/gtksource.h | 4 +-
gtksourceview/gtksourcecompletion-private.h | 33 +-
gtksourceview/gtksourcecompletion.c | 3740 ++++++--------------
gtksourceview/gtksourcecompletion.h | 96 +-
gtksourceview/gtksourcecompletion.ui | 123 -
gtksourceview/gtksourcecompletioncell-private.h | 32 +
gtksourceview/gtksourcecompletioncell.c | 419 +++
gtksourceview/gtksourcecompletioncell.h | 69 +
gtksourceview/gtksourcecompletioncontext-private.h | 36 +-
gtksourceview/gtksourcecompletioncontext.c | 1083 ++++--
gtksourceview/gtksourcecompletioncontext.h | 67 +-
gtksourceview/gtksourcecompletioninfo-private.h | 18 +-
gtksourceview/gtksourcecompletioninfo.c | 288 +-
gtksourceview/gtksourcecompletioninfo.h | 45 -
gtksourceview/gtksourcecompletionitem.c | 575 ---
gtksourceview/gtksourcecompletionitem.h | 70 -
gtksourceview/gtksourcecompletionlist-private.h | 48 +
gtksourceview/gtksourcecompletionlist.c | 563 +++
gtksourceview/gtksourcecompletionlist.ui | 74 +
gtksourceview/gtksourcecompletionlistbox-private.h | 56 +
gtksourceview/gtksourcecompletionlistbox.c | 1265 +++++++
.../gtksourcecompletionlistboxrow-private.h | 49 +
gtksourceview/gtksourcecompletionlistboxrow.c | 212 ++
gtksourceview/gtksourcecompletionlistboxrow.ui | 65 +
gtksourceview/gtksourcecompletionmodel-private.h | 90 -
gtksourceview/gtksourcecompletionmodel.c | 1240 -------
gtksourceview/gtksourcecompletionproposal.c | 311 +-
gtksourceview/gtksourcecompletionproposal.h | 79 +-
gtksourceview/gtksourcecompletionprovider.c | 503 +--
gtksourceview/gtksourcecompletionprovider.h | 187 +-
gtksourceview/gtksourcetypes-private.h | 6 +-
gtksourceview/gtksourcetypes.h | 4 +-
gtksourceview/gtksourceview.c | 26 +-
gtksourceview/gtksourceview.gresource.xml | 1 -
gtksourceview/meson.build | 12 +-
36 files changed, 5269 insertions(+), 6232 deletions(-)
---
diff --git a/gtksourceview/completion-providers/words/meson.build
b/gtksourceview/completion-providers/words/meson.build
index 29b4d449..b169b746 100644
--- a/gtksourceview/completion-providers/words/meson.build
+++ b/gtksourceview/completion-providers/words/meson.build
@@ -4,15 +4,15 @@ completionwords_c_args = [
]
completionwords_public_h = [
- 'gtksourcecompletionwords.h',
+ # 'gtksourcecompletionwords.h',
]
completionwords_public_c = [
- 'gtksourcecompletionwords.c',
- 'gtksourcecompletionwordsbuffer.c',
- 'gtksourcecompletionwordslibrary.c',
- 'gtksourcecompletionwordsproposal.c',
- 'gtksourcecompletionwordsutils.c',
+ # 'gtksourcecompletionwords.c',
+ # 'gtksourcecompletionwordsbuffer.c',
+ # 'gtksourcecompletionwordslibrary.c',
+ # 'gtksourcecompletionwordsproposal.c',
+ # 'gtksourcecompletionwordsutils.c',
]
install_headers(
diff --git a/gtksourceview/gtksource.h b/gtksourceview/gtksource.h
index b0434691..d54bccd5 100644
--- a/gtksourceview/gtksource.h
+++ b/gtksourceview/gtksource.h
@@ -22,9 +22,9 @@
#include "completion-providers/words/gtksourcecompletionwords.h"
#include "gtksourcetypes.h"
#include "gtksourcebuffer.h"
-#include "gtksourcecompletioncontext.h"
#include "gtksourcecompletion.h"
-#include "gtksourcecompletionitem.h"
+#include "gtksourcecompletioncell.h"
+#include "gtksourcecompletioncontext.h"
#include "gtksourcecompletionproposal.h"
#include "gtksourcecompletionprovider.h"
#include "gtksourceencoding.h"
diff --git a/gtksourceview/gtksourcecompletion-private.h b/gtksourceview/gtksourcecompletion-private.h
index c7853f48..bae3bdfe 100644
--- a/gtksourceview/gtksourcecompletion-private.h
+++ b/gtksourceview/gtksourcecompletion-private.h
@@ -1,7 +1,7 @@
/*
* This file is part of GtkSourceView
*
- * Copyright 2009 - Jesse van den Kieboom <jessevdk gnome org>
+ * Copyright 2020 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
@@ -15,22 +15,29 @@
*
* 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 <gtk/gtk.h>
-#include "gtksourcetypes.h"
+#include "gtksourcecompletion.h"
+#include "gtksourcecompletionlist-private.h"
+
+G_BEGIN_DECLS
+
+GtkSourceCompletion *_gtk_source_completion_new (GtkSourceView *view);
+GtkSourceCompletionList *_gtk_source_completion_get_display (GtkSourceCompletion *self);
+void _gtk_source_completion_set_font_desc (GtkSourceCompletion *self,
+ const PangoFontDescription *font_desc);
+void _gtk_source_completion_activate (GtkSourceCompletion *self,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProvider *provider,
+ GtkSourceCompletionProposal *proposal);
+gboolean _gtk_source_completion_get_select_on_show (GtkSourceCompletion *self);
+void _gtk_source_completion_css_changed (GtkSourceCompletion *self,
+ GtkCssStyleChange *change);
-G_GNUC_INTERNAL
-GtkSourceCompletion *_gtk_source_completion_new (GtkSourceView *source_view);
-G_GNUC_INTERNAL
-void _gtk_source_completion_add_proposals (GtkSourceCompletion *completion,
- GtkSourceCompletionContext *context,
- GtkSourceCompletionProvider *provider,
- GList *proposals,
- gboolean finished);
-G_GNUC_INTERNAL
-void _gtk_source_completion_css_changed (GtkSourceCompletion *completion,
- GtkCssStyleChange *change);
+G_END_DECLS
diff --git a/gtksourceview/gtksourcecompletion.c b/gtksourceview/gtksourcecompletion.c
index a825b358..569a64ff 100644
--- a/gtksourceview/gtksourcecompletion.c
+++ b/gtksourceview/gtksourcecompletion.c
@@ -1,9 +1,7 @@
/*
* This file is part of GtkSourceView
*
- * Copyright 2007-2009 Jesús Barbero Rodríguez <chuchiperriman gmail com>
- * Copyright 2009 Jesse van den Kieboom <jessevdk gnome org>
- * Copyright 2013 Sébastien Wilmet <swilmet gnome org>
+ * Copyright 2020 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
@@ -17,2214 +15,890 @@
*
* 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/>.
- */
-
-/**
- * SECTION:completion
- * @title: GtkSourceCompletion
- * @short_description: Main Completion Object
- *
- * The completion system helps the user when he/she writes some text,
- * such as words, command names, functions, and suchlike. Proposals can
- * be shown, to complete the text the user is writing. Each proposal can
- * contain an additional piece of information (for example
- * documentation), that is displayed when the "Details" button is
- * clicked.
- *
- * Proposals are created via a #GtkSourceCompletionProvider. There can
- * be for example a provider to complete words (see
- * #GtkSourceCompletionWords), another provider for the completion of
- * function names, etc. To add a provider, call
- * gtk_source_completion_add_provider().
- *
- * When the completion is activated, a #GtkSourceCompletionContext object is
- * created. The providers are asked whether they match the context, with
- * gtk_source_completion_provider_match(). If a provider doesn't match the
- * context, it will not be visible in the completion window. On the
- * other hand, if the provider matches the context, its proposals will
- * be displayed.
- *
- * When several providers match, they are all shown in the completion
- * window, but one can switch between providers: see the
- * #GtkSourceCompletion::move-page signal. It is also possible to
- * activate the first proposals with key bindings, see the
- * #GtkSourceCompletion:accelerators property.
*
- * The #GtkSourceCompletionProposal interface represents a proposal.
- * The #GtkSourceCompletionItem class is a simple implementation of this
- * interface.
- *
- * If a proposal contains extra information (see
- * gtk_source_completion_provider_get_info_widget()), it will be
- * displayed in a #GtkSourceCompletionInfo window, which appears when
- * the "Details" button is clicked.
- *
- * A #GtkSourceCompletionInfo window can also be used to display
- * calltips. When no proposals are available, it can be useful to
- * display extra information like a function prototype (number of
- * parameters, types of parameters, etc).
- *
- * Each #GtkSourceView object is associated with a #GtkSourceCompletion
- * instance. This instance can be obtained with
- * gtk_source_view_get_completion(). The #GtkSourceView class contains also the
- * #GtkSourceView::show-completion signal.
- *
- * A same #GtkSourceCompletionProvider object can be used for several
- * #GtkSourceCompletion's.
- */
-
-/* Idea to improve the code: use a composite widget template. This class is not
- * a GtkWidget, so some refactoring needs to be done, to have a subclass of
- * GtkSourceCompletionInfo for the main completion window.
+ * SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
-#include <glib/gi18n-lib.h>
-
-#include "gtksourcecompletion.h"
+#include "gtksourcebindinggroup-private.h"
#include "gtksourcecompletion-private.h"
-#include "gtksourcecompletionmodel-private.h"
-#include "gtksourcecompletioncontext.h"
-#include "gtksourcecompletioninfo-private.h"
+#include "gtksourcecompletioncontext-private.h"
+#include "gtksourcecompletionlist-private.h"
#include "gtksourcecompletionproposal.h"
#include "gtksourcecompletionprovider.h"
-#include "gtksourcecompletioncontext-private.h"
#include "gtksourcebuffer.h"
-#include "gtksource-marshal.h"
-#include "gtksourceview.h"
-
-enum
-{
- SHOW,
- HIDE,
- POPULATE_CONTEXT,
-
- /* Actions */
- ACTIVATE_PROPOSAL,
- MOVE_CURSOR,
- MOVE_PAGE,
-
- N_SIGNALS
-};
+#include "gtksourcesignalgroup-private.h"
+#include "gtksourceview-private.h"
-enum
-{
- PROP_0,
- PROP_VIEW,
- PROP_REMEMBER_INFO_VISIBILITY,
- PROP_SELECT_ON_SHOW,
- PROP_SHOW_HEADERS,
- PROP_SHOW_ICONS,
- PROP_ACCELERATORS,
- PROP_AUTO_COMPLETE_DELAY,
- PROP_PROVIDER_PAGE_SIZE,
- PROP_PROPOSAL_PAGE_SIZE
-};
+#define DEFAULT_PAGE_SIZE 5
struct _GtkSourceCompletion
{
GObject parent_instance;
- GtkSourceCompletionInfo *main_window;
- GtkSourceCompletionInfo *info_window;
+ /* The GtkSourceView that we are providing results for. This can be
+ * used by providers to get a reference.
+ */
+ GtkSourceView *view;
- /* Bottom bar, containing the "Details" button and the selection image
- * and label. */
- GtkWidget *bottom_bar;
+ /* A cancellable that we'll monitor to cancel anything that is currently
+ * in-flight. This is reset to a new GCancellable after each time
+ * g_cancellable_cancel() is called.
+ */
+ GCancellable *cancellable;
- /* Image and label in the bottom bar, on the right, for showing which
- * provider(s) are selected. */
- GtkImage *selection_image;
- GtkLabel *selection_label;
+ /* An array of providers that have been registered. These will be queried
+ * when input is provided for completion.
+ */
+ GPtrArray *providers;
- /* Used for insensitive styling */
- GtkTreeView *insensitive;
+ /* If we are currently performing a completion, the context is stored
+ * here. It will be cleared as soon as it's no longer valid to
+ * (re)display.
+ */
+ GtkSourceCompletionContext *context;
- /* The default widget for the info window */
- GtkLabel *default_info;
+ /* The signal group is used to track changes to the context while it is
+ * our current context. That includes handling notification of the first
+ * result so that we can show the window, etc.
+ */
+ GtkSourceSignalGroup *context_signals;
- /* The "Details" button, for showing the info window */
- GtkToggleButton *info_button;
+ /* Signals to changes in the underlying GtkTextBuffer that we use to
+ * determine where and how we can do completion.
+ */
+ GtkSourceSignalGroup *buffer_signals;
- /* List of proposals */
- GtkTreeView *tree_view_proposals;
+ /* We need to track various events on the view to ensure that we don't
+ * activate at incorrect times.
+ */
+ GtkSourceSignalGroup *view_signals;
- GtkCellRenderer *cell_renderer_proposal;
+ /* The display popover for results */
+ GtkSourceCompletionList *display;
- /* Completion management */
+ /* The completion mark for alignment */
+ GtkTextMark *completion_mark;
- GtkSourceCompletionModel *model_proposals;
+ /* Our current event while processing so that we can get access to it
+ * from a callback back into the completion instance.
+ */
+ const GdkKeyEvent *current_event;
- GList *providers;
+ /* Our cached font description to apply to views. */
+ PangoFontDescription *font_desc;
- GtkSourceCompletionContext *context;
- GList *active_providers;
- GList *running_providers;
+ /* If we have a queued update to refilter after deletions, this will be
+ * set to the GSource id.
+ */
+ guint queued_update;
- guint show_timed_out_id;
+ /* This value is incremented/decremented based on if we need to suppress
+ * visibility of the completion window (and avoid doing queries).
+ */
+ guint block_count;
- GtkTextBuffer *buffer;
+ /* Re-entrancy protection for gtk_source_completion_show(). */
+ guint showing;
- GList *auto_completion_selection;
- GtkSourceCompletionContext *auto_completion_context;
+ /* The number of rows to display. This is propagated to the window if/when
+ * the window is created.
+ */
+ guint page_size;
- /* Number of times the interactive completion is blocked */
- guint block_interactive_num;
+ /* If we're currently being displayed */
+ guint shown : 1;
- /* Properties */
+ /* If we have a completion actively in play */
+ guint waiting_for_results : 1;
- /* Weak reference to the view. You must check if view != NULL before
- * using it.
- */
- GtkSourceView *view;
- guint num_accelerators;
- guint auto_complete_delay;
- guint proposal_page_size;
- guint provider_page_size;
+ /* If we should refilter after the in-flight context completes */
+ guint needs_refilter : 1;
- guint remember_info_visibility : 1;
+ /* If the first item is automatically selected */
guint select_on_show : 1;
- guint show_headers : 1;
- guint show_icons : 1;
-};
-static guint signals[N_SIGNALS];
+ guint disposed : 1;
+};
G_DEFINE_TYPE (GtkSourceCompletion, gtk_source_completion, G_TYPE_OBJECT)
+enum {
+ PROP_0,
+ PROP_BUFFER,
+ PROP_PAGE_SIZE,
+ PROP_SELECT_ON_SHOW,
+ PROP_VIEW,
+ N_PROPS
+};
+
+enum {
+ ACTIVATE,
+ PROVIDER_ADDED,
+ PROVIDER_REMOVED,
+ SHOW,
+ HIDE,
+ N_SIGNALS
+};
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
static void
-scroll_to_iter (GtkSourceCompletion *completion,
- GtkTreeIter *iter)
+display_show (GtkSourceCompletionList *display)
{
- GtkTreePath *path;
- GtkTreeIter prev_iter = *iter;
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST (display));
+
+ _gtk_source_completion_list_reposition (display);
+ gtk_widget_show (GTK_WIDGET (display));
+ gtk_widget_grab_focus (GTK_WIDGET (display));
+}
- path = gtk_tree_model_get_path (GTK_TREE_MODEL (completion->model_proposals),
- iter);
+static void
+display_hide (GtkSourceCompletionList *display)
+{
+ GtkWidget *view;
- gtk_tree_view_scroll_to_cell (completion->tree_view_proposals,
- path, NULL,
- FALSE, 0, 0);
- gtk_tree_path_free (path);
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST (display));
- if (gtk_source_completion_model_iter_previous (completion->model_proposals, &prev_iter) &&
- gtk_source_completion_model_iter_is_header (completion->model_proposals, &prev_iter))
- {
- /* If we want to scroll to the first proposal of a provider,
- * it's better to show the header too, if there is a header.
- * We first scroll to the proposal, and then to the
- * header, so we are sure that the proposal is visible.
- */
+ gtk_widget_hide (GTK_WIDGET (display));
- path = gtk_tree_model_get_path (GTK_TREE_MODEL (completion->model_proposals),
- &prev_iter);
+ view = gtk_widget_get_ancestor (GTK_WIDGET (display), GTK_SOURCE_TYPE_VIEW);
- gtk_tree_view_scroll_to_cell (completion->tree_view_proposals,
- path, NULL,
- FALSE, 0, 0);
- gtk_tree_path_free (path);
+ if (view != NULL)
+ {
+ gtk_widget_grab_focus (view);
}
}
-/* Returns %TRUE if a proposal is selected.
- * Call g_object_unref() on @provider and @proposal when no longer needed.
- */
static gboolean
-get_selected_proposal (GtkSourceCompletion *completion,
- GtkSourceCompletionProvider **provider,
- GtkSourceCompletionProposal **proposal)
+gtk_source_completion_is_blocked (GtkSourceCompletion *self)
{
- GtkTreeIter iter;
- GtkTreeSelection *selection;
+ GtkTextBuffer *buffer;
- selection = gtk_tree_view_get_selection (completion->tree_view_proposals);
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
- if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
- {
- return FALSE;
- }
+ return self->block_count > 0 ||
+ self->view == NULL ||
+ self->providers->len == 0 ||
+ !gtk_widget_get_visible (GTK_WIDGET (self->view)) ||
+ !gtk_widget_has_focus (GTK_WIDGET (self->view)) ||
+ !(buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->view))) ||
+ gtk_text_buffer_get_has_selection (buffer) ||
+ !GTK_SOURCE_IS_VIEW (self->view);
+}
- if (gtk_source_completion_model_iter_is_header (completion->model_proposals, &iter))
- {
- return FALSE;
- }
+static PangoFontDescription *
+create_font_description (GtkSourceCompletion *self)
+{
+ PangoFontDescription *font_desc;
+ PangoContext *context;
- if (provider != NULL)
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
+
+ if (self->view == NULL)
{
- gtk_tree_model_get (GTK_TREE_MODEL (completion->model_proposals), &iter,
- GTK_SOURCE_COMPLETION_MODEL_COLUMN_PROVIDER, provider,
- -1);
+ return NULL;
}
- if (proposal != NULL)
+ context = gtk_widget_get_pango_context (GTK_WIDGET (self->view));
+ font_desc = pango_font_description_copy (pango_context_get_font_description (context));
+
+ /*
+ * Work around issue where when a proposal provides "<b>markup</b>" and
+ * the weight is set in the font description, the <b> markup will not
+ * have it's weight respected. This seems to be happening because the
+ * weight mask is getting set in pango_font_description_from_string()
+ * even if the the value is set to normal. That matter is complicated
+ * because PangoAttrFontDesc and PangoAttrWeight will both have the
+ * same starting offset in the PangoLayout.
+ * https://bugzilla.gnome.org/show_bug.cgi?id=755968
+ */
+ if (PANGO_WEIGHT_NORMAL == pango_font_description_get_weight (font_desc))
{
- gtk_tree_model_get (GTK_TREE_MODEL (completion->model_proposals), &iter,
- GTK_SOURCE_COMPLETION_MODEL_COLUMN_PROPOSAL, proposal,
- -1);
+ pango_font_description_unset_fields (font_desc, PANGO_FONT_MASK_WEIGHT);
}
- return TRUE;
+ return g_steal_pointer (&font_desc);
}
-/* Returns %TRUE if the first proposal is selected. */
-static gboolean
-check_first_selected (GtkSourceCompletion *completion)
+gboolean
+_gtk_source_completion_get_select_on_show (GtkSourceCompletion *self)
{
- GtkTreeSelection *selection;
- GtkTreeIter iter;
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (self), FALSE);
- if (get_selected_proposal (completion, NULL, NULL) ||
- !completion->select_on_show)
- {
- return FALSE;
- }
+ return self->select_on_show;
+}
- if (!gtk_source_completion_model_first_proposal (completion->model_proposals, &iter))
- {
- return FALSE;
- }
+static void
+gtk_source_completion_set_select_on_show (GtkSourceCompletion *self,
+ gboolean select_on_show)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
- selection = gtk_tree_view_get_selection (completion->tree_view_proposals);
- gtk_tree_selection_select_iter (selection, &iter);
- scroll_to_iter (completion, &iter);
+ select_on_show = !!select_on_show;
- return TRUE;
+ if (self->select_on_show != select_on_show)
+ {
+ self->select_on_show = select_on_show;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SELECT_ON_SHOW]);
+ }
}
static void
-get_iter_at_insert (GtkSourceCompletion *completion,
- GtkTextIter *iter)
+gtk_source_completion_complete_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
{
- gtk_text_buffer_get_iter_at_mark (completion->buffer,
- iter,
- gtk_text_buffer_get_insert (completion->buffer));
-}
+ GtkSourceCompletionContext *context = (GtkSourceCompletionContext *)object;
+ GtkSourceCompletionList *list;
+ GtkSourceCompletion *self = user_data;
+ GError *error = NULL;
-static GList *
-select_providers (GList *providers,
- GtkSourceCompletionContext *context)
-{
- GtkTextIter context_iter;
- GList *selection = NULL;
- GList *l;
+ g_assert (GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
- if (!gtk_source_completion_context_get_iter (context, &context_iter))
+ if (self->context == context)
{
- return NULL;
+ self->waiting_for_results = FALSE;
}
- for (l = providers; l != NULL; l = l->next)
+ if (!_gtk_source_completion_context_complete_finish (context, result, &error))
{
- GtkSourceCompletionProvider *provider = l->data;
+ g_debug ("Completion failed to complete: %s", error->message);
+ goto cleanup;
+ }
- gboolean good_activation = (gtk_source_completion_provider_get_activation (provider) &
- gtk_source_completion_context_get_activation (context)) != 0;
+ if (context != self->context)
+ goto cleanup;
- if (good_activation &&
- gtk_source_completion_provider_match (provider, context))
- {
- selection = g_list_prepend (selection, provider);
- }
+ if (self->needs_refilter)
+ {
+ /*
+ * At this point, we've gotten our new results for the context. But we had
+ * new content come in since we fired that request. So we need to ask the
+ * providers to further reduce the list based on updated query text.
+ */
+ self->needs_refilter = FALSE;
+ _gtk_source_completion_context_refilter (context);
}
- return g_list_reverse (selection);
+ list = _gtk_source_completion_get_display (self);
+
+ if (!gtk_source_completion_context_get_empty (context))
+ display_show (list);
+ else
+ display_hide (list);
+
+cleanup:
+ g_clear_error (&error);
+ g_clear_object (&self);
}
-static gint
-minimum_auto_complete_delay (GtkSourceCompletion *completion,
- GList *providers)
+static void
+_gtk_source_completion_set_context (GtkSourceCompletion *self,
+ GtkSourceCompletionContext *context)
{
- gint min_delay = completion->auto_complete_delay;
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
+ g_assert (!context || GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
- while (providers != NULL)
+ if (g_set_object (&self->context, context))
{
- GtkSourceCompletionProvider *provider = providers->data;
- gint delay = gtk_source_completion_provider_get_interactive_delay (provider);
-
- if (0 <= delay && delay < min_delay)
- {
- min_delay = delay;
- }
-
- providers = g_list_next (providers);
+ g_clear_handle_id (&self->queued_update, g_source_remove);
+ gtk_source_signal_group_set_target (self->context_signals, context);
}
-
- return min_delay;
}
static void
-reset_completion (GtkSourceCompletion *completion)
+gtk_source_completion_cancel (GtkSourceCompletion *self)
{
- if (completion->show_timed_out_id != 0)
- {
- g_source_remove (completion->show_timed_out_id);
- completion->show_timed_out_id = 0;
- }
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION (self));
- if (completion->context != NULL)
+ /* Nothing can re-use in-flight results now */
+ self->waiting_for_results = FALSE;
+ self->needs_refilter = FALSE;
+
+ if (self->context != NULL)
{
- /* Inform providers of cancellation through the context */
- _gtk_source_completion_context_cancel (completion->context);
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+
+ _gtk_source_completion_set_context (self, NULL);
- g_clear_object (&completion->context);
+ if (self->display != NULL)
+ {
+ _gtk_source_completion_list_set_context (self->display, NULL);
+ gtk_widget_hide (GTK_WIDGET (self->display));
+ }
}
+}
- g_list_free (completion->running_providers);
- g_list_free (completion->active_providers);
- completion->running_providers = NULL;
- completion->active_providers = NULL;
+static inline gboolean
+is_symbol_char (gunichar ch)
+{
+ return ch == '_' || g_unichar_isalnum (ch);
}
-/* A separator is a character like (, a space etc. An _ is not a separator. */
static gboolean
-is_separator (const gunichar ch)
+gtk_source_completion_compute_bounds (GtkSourceCompletion *self,
+ GtkTextIter *begin,
+ GtkTextIter *end)
{
- if (g_unichar_isprint (ch) &&
- (g_unichar_isalnum (ch) || ch == g_utf8_get_char ("_")))
+ GtkTextBuffer *buffer;
+ GtkTextMark *insert;
+ gunichar ch = 0;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
+ g_assert (begin != NULL);
+ g_assert (end != NULL);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->view));
+ insert = gtk_text_buffer_get_insert (buffer);
+ gtk_text_buffer_get_iter_at_mark (buffer, end, insert);
+
+ *begin = *end;
+
+ do
{
- return FALSE;
+ if (!gtk_text_iter_backward_char (begin))
+ break;
+ ch = gtk_text_iter_get_char (begin);
}
+ while (is_symbol_char (ch));
- return TRUE;
+ if (ch && !is_symbol_char (ch))
+ {
+ gtk_text_iter_forward_char (begin);
+ }
+
+ return !gtk_text_iter_equal (begin, end);
}
-/* Assigns @start_word to the start position of the word, and @end_word to the
- * end position.
- */
static void
-get_word_iter (GtkTextBuffer *buffer,
- GtkTextIter *start_word,
- GtkTextIter *end_word)
+gtk_source_completion_start (GtkSourceCompletion *self,
+ GtkSourceCompletionActivation activation)
{
- gtk_text_buffer_get_iter_at_mark (buffer,
- end_word,
- gtk_text_buffer_get_insert (buffer));
+ g_autoptr(GtkSourceCompletionContext) context = NULL;
+ GtkTextBuffer *buffer;
+ GtkTextIter begin;
+ GtkTextIter end;
- *start_word = *end_word;
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
+ g_assert (self->context == NULL);
- while (gtk_text_iter_backward_char (start_word))
- {
- gunichar ch = gtk_text_iter_get_char (start_word);
+ g_clear_handle_id (&self->queued_update, g_source_remove);
- if (is_separator (ch))
- {
- gtk_text_iter_forward_char (start_word);
+ if (!gtk_source_completion_compute_bounds (self, &begin, &end))
+ {
+ if (activation == GTK_SOURCE_COMPLETION_ACTIVATION_INTERACTIVE)
return;
- }
+ begin = end;
}
-}
-static void
-replace_current_word (GtkTextBuffer *buffer,
- const gchar *new_text)
-{
- GtkTextIter word_start;
- GtkTextIter word_end;
+ context = _gtk_source_completion_context_new (self);
+ for (guint i = 0; i < self->providers->len; i++)
+ _gtk_source_completion_context_add_provider (context, g_ptr_array_index (self->providers, i));
+ _gtk_source_completion_set_context (self, context);
- get_word_iter (buffer, &word_start, &word_end);
+ self->waiting_for_results = TRUE;
+ self->needs_refilter = FALSE;
- gtk_text_buffer_begin_user_action (buffer);
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->view));
+ gtk_text_buffer_move_mark (buffer, self->completion_mark, &begin);
- gtk_text_buffer_delete (buffer, &word_start, &word_end);
+ _gtk_source_completion_context_complete_async (context,
+ activation,
+ &begin,
+ &end,
+ self->cancellable,
+ gtk_source_completion_complete_cb,
+ g_object_ref (self));
- if (new_text != NULL)
+ if (self->display != NULL)
{
- gtk_text_buffer_insert (buffer, &word_start, new_text, -1);
- }
+ _gtk_source_completion_list_set_context (self->display, context);
- gtk_text_buffer_end_user_action (buffer);
+ if (!gtk_source_completion_context_get_empty (context))
+ display_show (self->display);
+ else
+ display_hide (self->display);
+ }
}
static void
-update_window_position (GtkSourceCompletion *completion)
+gtk_source_completion_update (GtkSourceCompletion *self,
+ GtkSourceCompletionActivation activation)
{
- GtkSourceCompletionProvider *provider;
- GtkSourceCompletionProposal *proposal;
+ GtkTextBuffer *buffer;
+ GtkTextMark *insert;
+ GtkTextIter begin;
+ GtkTextIter end;
GtkTextIter iter;
- gboolean iter_set = FALSE;
- if (completion->view == NULL)
- {
- return;
- }
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
+ g_assert (self->context != NULL);
+ g_assert (GTK_SOURCE_IS_COMPLETION_CONTEXT (self->context));
- /* The model can be modified while there is no completion context, for
- * example when the headers are shown or hidden. This triggers a signal
- * to update the window position, but if there is no completion context,
- * no need to update the window position (the window is normally hidden
- * in this case). When a new population is done, this function will be
- * called again, so no problem.
+ /*
+ * First, find the boundary for the word we are trying to complete. We might
+ * be able to refine a previous query instead of making a new one which can
+ * save on a lot of backend work.
*/
- if (completion->context == NULL)
+ gtk_source_completion_compute_bounds (self, &begin, &end);
+
+ if (_gtk_source_completion_context_can_refilter (self->context, &begin, &end))
{
+ GtkSourceCompletionList *display = _gtk_source_completion_get_display (self);
+
+ /*
+ * Make sure we update providers that have already delivered results
+ * even though some of them won't be ready yet.
+ */
+ _gtk_source_completion_context_refilter (self->context);
+
+ /*
+ * If we're waiting for the results still to come in, then just mark
+ * that we need to do post-processing rather than trying to refilter now.
+ */
+ if (self->waiting_for_results)
+ {
+ self->needs_refilter = TRUE;
+ return;
+ }
+
+ if (!gtk_source_completion_context_get_empty (self->context))
+ display_show (display);
+ else
+ display_hide (display);
+
return;
}
- if (get_selected_proposal (completion, &provider, &proposal))
+ if (!gtk_source_completion_context_get_bounds (self->context, &begin, &end) ||
+ gtk_text_iter_equal (&begin, &end))
{
- GtkTextIter context_iter;
- gboolean valid_context;
-
- valid_context = gtk_source_completion_context_get_iter (completion->context,
- &context_iter);
-
- if (valid_context &&
- gtk_source_completion_provider_get_start_iter (provider,
- completion->context,
- proposal,
- &iter))
+ if (activation == GTK_SOURCE_COMPLETION_ACTIVATION_INTERACTIVE)
{
- iter_set = TRUE;
+ gtk_source_completion_hide (self);
+ return;
}
- g_object_unref (provider);
- g_object_unref (proposal);
+ goto reset;
}
- if (!iter_set)
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->view));
+ insert = gtk_text_buffer_get_insert (buffer);
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+
+ /*
+ * If our completion prefix bounds match the prefix that we looked
+ * at previously, we can possibly refilter the previous context instead
+ * of creating a new context.
+ */
+
+ /*
+ * The context uses GtkTextMark which should have been advanced as
+ * the user continued to type. So if @end matches @iter (our insert
+ * location), then we can possibly update the previous context by
+ * further refining the query to a subset of the result.
+ */
+ if (gtk_text_iter_equal (&iter, &end))
{
- GtkTextIter end_word;
- get_word_iter (completion->buffer, &iter, &end_word);
+ gtk_source_completion_show (self);
+ return;
}
- gtk_source_completion_info_move_to_iter (completion->main_window,
- GTK_TEXT_VIEW (completion->view),
- &iter);
+reset:
+ gtk_source_completion_cancel (self);
+ gtk_source_completion_start (self, activation);
}
static void
-set_info_widget (GtkSourceCompletion *completion,
- GtkWidget *new_widget)
+gtk_source_completion_real_hide (GtkSourceCompletion *self)
{
- GtkWidget *cur_widget = gtk_window_get_child (GTK_WINDOW (completion->info_window));
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
- if (cur_widget != new_widget)
+ if (self->display != NULL)
{
- gtk_window_set_child (GTK_WINDOW (completion->info_window), new_widget);
+ gtk_widget_hide (GTK_WIDGET (self->display));
}
}
static void
-update_proposal_info_state (GtkSourceCompletion *completion)
+gtk_source_completion_real_show (GtkSourceCompletion *self)
{
- GtkSourceCompletionProvider *provider = NULL;
- GtkSourceCompletionProposal *proposal = NULL;
- GtkWidget *info_widget;
+ GtkSourceCompletionList *display;
- if (!get_selected_proposal (completion, &provider, &proposal))
- {
- gtk_widget_set_sensitive (GTK_WIDGET (completion->info_button), FALSE);
- return;
- }
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
- info_widget = gtk_source_completion_provider_get_info_widget (provider, proposal);
+ display = _gtk_source_completion_get_display (self);
- if (info_widget != NULL)
+ if (self->context == NULL)
{
- set_info_widget (completion, info_widget);
- gtk_widget_set_sensitive (GTK_WIDGET (completion->info_button), TRUE);
-
- gtk_source_completion_provider_update_info (provider,
- proposal,
- info_widget);
+ gtk_source_completion_start (self, GTK_SOURCE_COMPLETION_ACTIVATION_USER_REQUESTED);
}
else
{
- gchar *text = gtk_source_completion_proposal_get_info (proposal);
+ gtk_source_completion_update (self, GTK_SOURCE_COMPLETION_ACTIVATION_USER_REQUESTED);
+ }
- if (text != NULL)
- {
- set_info_widget (completion, GTK_WIDGET (completion->default_info));
- gtk_widget_set_sensitive (GTK_WIDGET (completion->info_button), TRUE);
+ _gtk_source_completion_list_set_context (display, self->context);
- gtk_label_set_markup (completion->default_info, text);
- g_free (text);
- }
- else
- {
- gtk_widget_set_sensitive (GTK_WIDGET (completion->info_button), FALSE);
- }
- }
+ if (!gtk_source_completion_context_get_empty (self->context))
+ display_show (display);
+ else
+ display_hide (display);
+}
+
+static gboolean
+gtk_source_completion_queued_update_cb (gpointer user_data)
+{
+ GtkSourceCompletion *self = user_data;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
+
+ self->queued_update = 0;
+
+ if (self->context != NULL)
+ gtk_source_completion_update (self, GTK_SOURCE_COMPLETION_ACTIVATION_INTERACTIVE);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+gtk_source_completion_queue_update (GtkSourceCompletion *self)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
+
+ g_clear_handle_id (&self->queued_update, g_source_remove);
+
+ /*
+ * We hit this code path when the user has deleted text. We want to
+ * introduce just a bit of delay so that deleting under heavy key
+ * repeat will not stall doing lots of refiltering.
+ */
- g_object_unref (provider);
- g_object_unref (proposal);
+ self->queued_update =
+ g_timeout_add_full (G_PRIORITY_LOW,
+ 16.7*2 + 1,
+ gtk_source_completion_queued_update_cb,
+ self,
+ NULL);
}
static void
-update_info_window_visibility (GtkSourceCompletion *completion)
+gtk_source_completion_notify_context_empty_cb (GtkSourceCompletion *self,
+ GParamSpec *pspec,
+ GtkSourceCompletionContext *context)
{
- if (gtk_widget_get_sensitive (GTK_WIDGET (completion->info_button)) &&
- gtk_toggle_button_get_active (completion->info_button))
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
+ g_assert (pspec != NULL);
+ g_assert (GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
+
+ if (context != self->context)
+ return;
+
+ if (gtk_source_completion_context_get_empty (context))
{
- gtk_widget_show (GTK_WIDGET (completion->info_window));
+ if (self->display != NULL)
+ display_hide (self->display);
}
else
{
- gtk_widget_hide (GTK_WIDGET (completion->info_window));
+ GtkSourceCompletionList *display = _gtk_source_completion_get_display (self);
+ display_show (display);
}
}
static void
-update_proposal_info (GtkSourceCompletion *completion)
+gtk_source_completion_buffer_delete_range_after_cb (GtkSourceCompletion *self,
+ GtkTextIter *begin,
+ GtkTextIter *end,
+ GtkTextBuffer *buffer)
{
- update_proposal_info_state (completion);
- update_info_window_visibility (completion);
-}
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
+ g_assert (GTK_SOURCE_IS_VIEW (self->view));
+ g_assert (begin != NULL);
+ g_assert (end != NULL);
+ g_assert (GTK_IS_TEXT_BUFFER (buffer));
-static void
-gtk_source_completion_show_default (GtkSourceCompletion *completion)
-{
- if (completion->view == NULL)
+ if (self->context != NULL)
{
- return;
- }
+ if (!gtk_source_completion_is_blocked (self))
+ {
+ GtkTextIter b, e;
- gtk_widget_show (GTK_WIDGET (completion->main_window));
+ gtk_source_completion_context_get_bounds (self->context, &b, &e);
- /* Do the autosize when the widget is visible. It doesn't work if it is
- * done before.
- */
- gtk_tree_view_columns_autosize (completion->tree_view_proposals);
+ /*
+ * If they just backspaced all of the text, then we want to just hide
+ * the completion window since that can get a bit intrusive.
+ */
+ if (gtk_text_iter_equal (&b, &e))
+ {
+ g_clear_handle_id (&self->queued_update, g_source_remove);
+ gtk_source_completion_cancel (self);
+ return;
+ }
- if (!completion->remember_info_visibility)
- {
- gtk_toggle_button_set_active (completion->info_button, FALSE);
+ gtk_source_completion_queue_update (self);
+ }
}
-
- update_proposal_info (completion);
-
- gtk_widget_grab_focus (GTK_WIDGET (completion->view));
}
static void
-gtk_source_completion_hide_default (GtkSourceCompletion *completion)
-{
- gtk_widget_hide (GTK_WIDGET (completion->info_window));
- gtk_widget_hide (GTK_WIDGET (completion->main_window));
-}
+gtk_source_completion_view_move_cursor_cb (GtkSourceCompletion *self,
+ GtkMovementStep step,
+ gint count,
+ gboolean extend_selection,
+ GtkSourceView *view)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
+ g_assert (GTK_SOURCE_IS_VIEW (view));
+
+ /* TODO: Should we keep the context alive while we begin a new one?
+ * Or rather, how can we avoid the hide/show of the widget that
+ * could result in flicker?
+ */
-static void
-gtk_source_completion_proposals_size_allocate (GtkSourceCompletion *completion,
- GtkAllocation *allocation,
- GtkWidget *widget)
-{
- const gint horizontal_separator = 4; /* From _TREE_VIEW_HORIZONTAL_SEPARATOR */
- GtkTreeViewColumn *column;
- gint cell_offset = 0;
- gint column_offset;
- gint x_offset = 0;
- gdouble x_translated;
-
- if (!gtk_widget_get_realized (GTK_WIDGET (completion->tree_view_proposals)))
+ if (self->display != NULL &&
+ gtk_widget_get_visible (GTK_WIDGET (self->display)))
{
- return;
+ gtk_source_completion_cancel (self);
}
+}
- column = gtk_tree_view_get_column (completion->tree_view_proposals, 1);
- column_offset = gtk_tree_view_column_get_x_offset (column);
- gtk_tree_view_column_cell_get_position (column,
- completion->cell_renderer_proposal,
- &cell_offset,
- NULL);
-
- x_offset = column_offset + cell_offset + horizontal_separator;
-
- gtk_tree_view_convert_bin_window_to_widget_coords (completion->tree_view_proposals,
- x_offset,
- 0,
- &x_offset,
- NULL);
- gtk_widget_translate_coordinates (GTK_WIDGET (completion->tree_view_proposals),
- GTK_WIDGET (completion->main_window),
- x_offset,
- 0,
- &x_translated,
- NULL);
-
- _gtk_source_completion_info_set_xoffset (completion->main_window, -x_translated);
+static gboolean
+is_single_char (const gchar *text,
+ gint len)
+{
+ if (len == 1)
+ return TRUE;
+ else if (len > 6)
+ return FALSE;
+ else
+ return g_utf8_strlen (text, len) == 1;
}
static void
-gtk_source_completion_activate_proposal (GtkSourceCompletion *completion)
+gtk_source_completion_buffer_insert_text_after_cb (GtkSourceCompletion *self,
+ GtkTextIter *iter,
+ const gchar *text,
+ gint len,
+ GtkTextBuffer *buffer)
{
- GtkSourceCompletionProvider *provider = NULL;
- GtkSourceCompletionProposal *proposal = NULL;
- GtkTextIter insert_iter;
- GtkTextIter context_iter;
- gboolean valid_context;
- gboolean activated;
-
- if (completion->view == NULL)
+ GtkSourceCompletionActivation activation = GTK_SOURCE_COMPLETION_ACTIVATION_INTERACTIVE;
+ GtkTextIter begin;
+ GtkTextIter end;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
+ g_assert (iter != NULL);
+ g_assert (text != NULL);
+ g_assert (len > 0);
+ g_assert (GTK_IS_TEXT_BUFFER (buffer));
+
+ g_clear_handle_id (&self->queued_update, g_source_remove);
+
+ if (gtk_source_completion_is_blocked (self) || !is_single_char (text, len))
{
+ gtk_source_completion_cancel (self);
return;
}
- if (!get_selected_proposal (completion, &provider, &proposal))
+ if (!gtk_source_completion_compute_bounds (self, &begin, &end))
{
+ GtkTextIter cur = end;
+
+ if (gtk_text_iter_backward_char (&cur))
+ {
+ gunichar ch = gtk_text_iter_get_char (&cur);
+
+ for (guint i = 0; i < self->providers->len; i++)
+ {
+ GtkSourceCompletionProvider *provider = g_ptr_array_index (self->providers,
i);
+
+ if (gtk_source_completion_provider_is_trigger (provider, &end, ch))
+ {
+ /*
+ * We got a trigger, but we failed to continue the bounds of a
previous
+ * completion. We need to cancel the previous completion (if any)
first
+ * and then try to start a new completion due to trigger.
+ */
+ gtk_source_completion_cancel (self);
+ activation = GTK_SOURCE_COMPLETION_ACTIVATION_INTERACTIVE;
+ goto do_completion;
+ }
+ }
+ }
+
+ gtk_source_completion_cancel (self);
return;
}
- get_iter_at_insert (completion, &insert_iter);
+do_completion:
- gtk_source_completion_block_interactive (completion);
+ if (self->context == NULL)
+ gtk_source_completion_start (self, activation);
+ else
+ gtk_source_completion_update (self, activation);
+}
- activated = gtk_source_completion_provider_activate_proposal (provider, proposal, &insert_iter);
+static void
+gtk_source_completion_buffer_mark_set_cb (GtkSourceCompletion *self,
+ const GtkTextIter *iter,
+ GtkTextMark *mark,
+ GtkTextBuffer *buffer)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
+ g_assert (GTK_IS_TEXT_MARK (mark));
+ g_assert (GTK_IS_TEXT_BUFFER (buffer));
- valid_context = (completion->context != NULL &&
- gtk_source_completion_context_get_iter (completion->context,
- &context_iter));
+ if (mark != gtk_text_buffer_get_insert (buffer))
+ return;
- if (!activated && valid_context)
+ if (_gtk_source_completion_context_iter_invalidates (self->context, iter))
{
- GtkTextIter start_iter;
- gchar *text = gtk_source_completion_proposal_get_text (proposal);
-
- gboolean has_start = gtk_source_completion_provider_get_start_iter (provider,
- completion->context,
- proposal,
- &start_iter);
-
- if (has_start)
- {
- gtk_text_buffer_begin_user_action (completion->buffer);
- gtk_text_buffer_delete (completion->buffer, &start_iter, &insert_iter);
- gtk_text_buffer_insert (completion->buffer, &start_iter, text, -1);
- gtk_text_buffer_end_user_action (completion->buffer);
- }
- else
- {
- replace_current_word (completion->buffer, text);
- }
-
- g_free (text);
- }
-
- gtk_source_completion_unblock_interactive (completion);
-
- gtk_source_completion_hide (completion);
-
- g_object_unref (provider);
- g_object_unref (proposal);
-}
-
-static void
-update_info_position (GtkSourceCompletion *completion)
-{
- GdkSurface *main_surface;
- GdkSurface *info_surface;
- GdkSurface *view_surface;
- GdkPopupLayout *layout;
- GtkRoot *root;
- GdkRectangle geom;
-
- if (!GTK_IS_NATIVE (completion->main_window) ||
- !(main_surface = gtk_native_get_surface (GTK_NATIVE (completion->main_window))) ||
- !GTK_IS_NATIVE (completion->info_window) ||
- !(info_surface = gtk_native_get_surface (GTK_NATIVE (completion->info_window))) ||
- !(root = gtk_widget_get_root (GTK_WIDGET (completion->view))) ||
- !GTK_IS_NATIVE (root) ||
- !(view_surface = gtk_native_get_surface (GTK_NATIVE (root))))
- {
- return;
- }
-
- geom.x = gdk_popup_get_position_x (GDK_POPUP (main_surface));
- geom.y = gdk_popup_get_position_y (GDK_POPUP (main_surface));
- geom.width = gdk_surface_get_width (main_surface);
- geom.height = gdk_surface_get_height (main_surface);
-
- layout = gdk_popup_layout_new (&geom, GDK_GRAVITY_NORTH_EAST, GDK_GRAVITY_NORTH_WEST);
- gdk_popup_layout_set_anchor_hints (layout, GDK_ANCHOR_FLIP_X);
- gdk_popup_present (GDK_POPUP (info_surface), geom.width, geom.height, layout);
- gdk_popup_layout_unref (layout);
-}
-
-static GtkSourceCompletionProvider *
-get_visible_provider (GtkSourceCompletion *completion)
-{
- GList *visible = gtk_source_completion_model_get_visible_providers (completion->model_proposals);
-
- if (visible != NULL)
- {
- return GTK_SOURCE_COMPLETION_PROVIDER (visible->data);
- }
- else
- {
- return NULL;
- }
-}
-
-static void
-get_num_visible_providers (GtkSourceCompletion *completion,
- guint *num,
- guint *current)
-{
- GList *providers = gtk_source_completion_model_get_providers (completion->model_proposals);
- GtkSourceCompletionProvider *visible = get_visible_provider (completion);
-
- *num = g_list_length (providers);
- *current = 0;
-
- if (visible != NULL)
- {
- gint idx = g_list_index (providers, visible);
- g_return_if_fail (idx != -1);
-
- *current = idx + 1;
- }
-
- g_list_free (providers);
-}
-
-static void
-update_selection_label (GtkSourceCompletion *completion)
-{
- guint pos;
- guint num;
- gchar *name;
- gchar *selection_text;
- GtkSourceCompletionProvider *visible;
-
- get_num_visible_providers (completion, &num, &pos);
-
- if (num <= 1)
- {
- /* At most one provider. All the proposals are shown. */
- gtk_image_clear (completion->selection_image);
- gtk_widget_hide (GTK_WIDGET (completion->selection_label));
- return;
- }
-
- visible = get_visible_provider (completion);
-
- if (visible == NULL)
- {
- /* Translators: "All" is used as a label in the status bar of the
- popup, telling that all completion pages are shown. */
- name = g_strdup_printf("<b>%s</b>", _("All"));
-
- gtk_image_clear (completion->selection_image);
- }
- else
- {
- GdkTexture *texture;
-
- gchar *temp_name = gtk_source_completion_provider_get_name (visible);
- name = g_markup_escape_text (temp_name, -1);
- g_free (temp_name);
-
- texture = gtk_source_completion_provider_get_icon (visible);
- gtk_image_set_from_paintable (completion->selection_image, GDK_PAINTABLE (texture));
- }
-
- selection_text = g_strdup_printf ("<small>%s (%d/%d)</small>", name, pos + 1, num + 1);
- gtk_label_set_markup (completion->selection_label, selection_text);
- gtk_widget_show (GTK_WIDGET (completion->selection_label));
-
- g_free (selection_text);
- g_free (name);
-}
-
-static gboolean
-get_next_iter (GtkSourceCompletion *completion,
- gint num,
- GtkTreeIter *iter)
-{
- GtkTreeSelection *selection;
- gboolean has_selection;
-
- selection = gtk_tree_view_get_selection (completion->tree_view_proposals);
- has_selection = gtk_tree_selection_get_selected (selection, NULL, iter);
-
- if (!has_selection)
- {
- return gtk_source_completion_model_first_proposal (completion->model_proposals,
- iter);
- }
-
- while (num > 0)
- {
- if (!gtk_source_completion_model_next_proposal (completion->model_proposals, iter))
- {
- return gtk_source_completion_model_last_proposal (completion->model_proposals,
- iter);
- }
-
- num--;
- }
-
- return TRUE;
-}
-
-static gboolean
-get_previous_iter (GtkSourceCompletion *completion,
- gint num,
- GtkTreeIter *iter)
-{
- GtkTreeSelection *selection;
- gboolean has_selection;
-
- selection = gtk_tree_view_get_selection (completion->tree_view_proposals);
- has_selection = gtk_tree_selection_get_selected (selection, NULL, iter);
-
- if (!has_selection)
- {
- return gtk_source_completion_model_last_proposal (completion->model_proposals,
- iter);
- }
-
- while (num > 0)
- {
- if (!gtk_source_completion_model_previous_proposal (completion->model_proposals,
- iter))
- {
- return gtk_source_completion_model_first_proposal (completion->model_proposals,
- iter);
- }
-
- num--;
- }
-
- return TRUE;
-}
-
-static void
-gtk_source_completion_move_cursor (GtkSourceCompletion *completion,
- GtkScrollStep step,
- gint num)
-{
- GtkTreeIter iter;
- gboolean ok;
-
- if (step == GTK_SCROLL_ENDS)
- {
- if (num > 0)
- {
- ok = gtk_source_completion_model_last_proposal (completion->model_proposals,
- &iter);
- }
- else
- {
- ok = gtk_source_completion_model_first_proposal (completion->model_proposals,
- &iter);
- }
- }
- else
- {
- if (step == GTK_SCROLL_PAGES)
- {
- num *= completion->proposal_page_size;
- }
-
- if (num > 0)
- {
- ok = get_next_iter (completion, num, &iter);
- }
- else
- {
- ok = get_previous_iter (completion, -1 * num, &iter);
- }
- }
-
- if (ok)
- {
- GtkTreeSelection *selection;
-
- selection = gtk_tree_view_get_selection (completion->tree_view_proposals);
- gtk_tree_selection_select_iter (selection, &iter);
-
- scroll_to_iter (completion, &iter);
- }
-}
-
-static GList *
-get_last_provider (GtkSourceCompletion *completion)
-{
- GList *providers = gtk_source_completion_model_get_providers (completion->model_proposals);
- GList *ret;
-
- g_return_val_if_fail (providers != NULL, NULL);
-
- if (providers->next == NULL)
- {
- ret = NULL;
- }
- else
- {
- ret = g_list_copy (g_list_last (providers));
- }
-
- g_list_free (providers);
- return ret;
-}
-
-static GList *
-providers_cycle_forward (GList *all_providers,
- GList *position,
- gint num)
-{
- GList *l = position;
- gint i;
-
- if (all_providers == NULL || all_providers->next == NULL)
- {
- return NULL;
- }
-
- for (i = 0; i < num; i++)
- {
- l = l == NULL ? all_providers : l->next;
- }
-
- return l;
-}
-
-static GList *
-providers_cycle_backward (GList *all_providers,
- GList *position,
- gint num)
-{
- gint i;
- GList *l = position;
- GList *end = g_list_last (all_providers);
-
- if (all_providers == NULL || all_providers->next == NULL)
- {
- return NULL;
- }
-
- for (i = 0; i < num; i++)
- {
- l = l == NULL ? end : l->prev;
- }
-
- return l;
-}
-
-static GList *
-get_next_provider (GtkSourceCompletion *completion,
- gint num)
-{
- GList *providers;
- GList *visible_providers;
- GList *position;
- GList *ret;
-
- providers = gtk_source_completion_model_get_providers (completion->model_proposals);
- visible_providers = gtk_source_completion_model_get_visible_providers (completion->model_proposals);
-
- if (visible_providers == NULL)
- {
- position = NULL;
- }
- else
- {
- position = g_list_find (providers, visible_providers->data);
- }
-
- position = providers_cycle_forward (providers, position, num);
-
- if (position == NULL)
- {
- ret = NULL;
- }
- else
- {
- ret = g_list_append (NULL, position->data);
- }
-
- g_list_free (providers);
-
- return ret;
-}
-
-static GList *
-get_previous_provider (GtkSourceCompletion *completion,
- gint num)
-{
- GList *providers;
- GList *visible_providers;
- GList *position;
- GList *ret;
-
- providers = gtk_source_completion_model_get_providers (completion->model_proposals);
- visible_providers = gtk_source_completion_model_get_visible_providers (completion->model_proposals);
-
- if (visible_providers == NULL)
- {
- position = NULL;
- }
- else
- {
- position = g_list_find (providers, visible_providers->data);
- }
-
- position = providers_cycle_backward (providers, position, num);
-
- if (position == NULL)
- {
- ret = NULL;
- }
- else
- {
- ret = g_list_append (NULL, position->data);
- }
-
- g_list_free (providers);
-
- return ret;
-}
-
-static void
-gtk_source_completion_move_page (GtkSourceCompletion *completion,
- GtkScrollStep step,
- gint num)
-{
- GList *visible_providers = NULL;
-
- if (step == GTK_SCROLL_ENDS)
- {
- if (num > 0)
- {
- visible_providers = get_last_provider (completion);
- }
- else
- {
- visible_providers = NULL;
- }
- }
- else
- {
- if (step == GTK_SCROLL_PAGES)
- {
- num *= completion->provider_page_size;
- }
-
- if (num > 0)
- {
- visible_providers = get_next_provider (completion, num);
- }
- else
- {
- visible_providers = get_previous_provider (completion, -1 * num);
- }
- }
-
- gtk_tree_view_set_model (completion->tree_view_proposals, NULL);
- gtk_tree_view_columns_autosize (completion->tree_view_proposals);
-
- gtk_source_completion_model_set_visible_providers (completion->model_proposals,
- visible_providers);
-
- gtk_tree_view_set_model (completion->tree_view_proposals,
- GTK_TREE_MODEL (completion->model_proposals));
-
- update_selection_label (completion);
- check_first_selected (completion);
-
- g_list_free (visible_providers);
-}
-
-/* Begins at 0. Returns -1 if no accelerators available for @iter. */
-static gint
-get_accel_at_iter (GtkSourceCompletion *completion,
- GtkTreeIter *iter)
-{
- GtkTreeIter it;
- guint accel;
-
- if (gtk_source_completion_model_iter_is_header (completion->model_proposals, iter))
- {
- return -1;
- }
-
- if (!gtk_source_completion_model_first_proposal (completion->model_proposals, &it))
- {
- g_return_val_if_reached (-1);
- }
-
- for (accel = 0; accel < completion->num_accelerators; accel++)
- {
- if (gtk_source_completion_model_iter_equal (completion->model_proposals,
- iter,
- &it))
- {
- return accel;
- }
-
- if (!gtk_source_completion_model_next_proposal (completion->model_proposals, &it))
- {
- return -1;
- }
- }
-
- return -1;
-}
-
-static void
-render_proposal_accelerator_func (GtkTreeViewColumn *column,
- GtkCellRenderer *cell,
- GtkTreeModel *model,
- GtkTreeIter *iter,
- GtkSourceCompletion *completion)
-{
- gint accel = get_accel_at_iter (completion, iter);
- gchar *text = NULL;
-
- if (accel != -1)
- {
- text = g_strdup_printf ("<small><b>%d</b></small>", (accel + 1) % 10);
- }
-
- g_object_set (cell, "markup", text, NULL);
- g_free (text);
-}
-
-static gboolean
-activate_by_accelerator (GtkSourceCompletion *completion,
- gint num)
-{
- GtkTreeSelection *selection;
- GtkTreeIter iter;
- gint i;
-
- if (completion->num_accelerators == 0)
- {
- return FALSE;
- }
-
- num = num == 0 ? 9 : num - 1;
-
- if (num < 0 || completion->num_accelerators <= (guint)num)
- {
- return FALSE;
- }
-
- if (!gtk_source_completion_model_first_proposal (completion->model_proposals, &iter))
- {
- return FALSE;
- }
-
- for (i = 0; i < num; i++)
- {
- if (!gtk_source_completion_model_next_proposal (completion->model_proposals, &iter))
- {
- return FALSE;
- }
- }
-
- selection = gtk_tree_view_get_selection (completion->tree_view_proposals);
- gtk_tree_selection_select_iter (selection, &iter);
- gtk_source_completion_activate_proposal (completion);
-
- return TRUE;
-}
-
-static void
-selection_changed_cb (GtkTreeSelection *selection,
- GtkSourceCompletion *completion)
-{
- update_proposal_info (completion);
-
- if (get_selected_proposal (completion, NULL, NULL))
- {
- update_window_position (completion);
- }
-}
-
-static gboolean
-hide_completion_cb (GtkSourceCompletion *completion)
-{
- gtk_source_completion_hide (completion);
- return FALSE;
-}
-
-static gboolean
-view_key_press_event_cb (GtkEventController *key,
- guint keyval,
- guint keycode,
- GdkModifierType state,
- GtkSourceCompletion *completion)
-{
-#if 0
- static gboolean mnemonic_keyval_set = FALSE;
- static guint mnemonic_keyval = GDK_KEY_VoidSymbol;
- GdkModifierType mod;
- GtkBindingSet *binding_set;
-
- g_assert (GTK_IS_EVENT_CONTROLLER_KEY (key));
- g_assert (GTK_SOURCE_IS_COMPLETION (completion));
-
- if (!gtk_widget_get_visible (GTK_WIDGET (completion->main_window)))
- {
- return FALSE;
- }
-
- if (G_UNLIKELY (!mnemonic_keyval_set))
- {
- const gchar *label_text = gtk_button_get_label (GTK_BUTTON (completion->info_button));
- GtkWidget *label = gtk_label_new_with_mnemonic (label_text);
- g_object_ref_sink (label);
-
- mnemonic_keyval = gtk_label_get_mnemonic_keyval (GTK_LABEL (label));
- mnemonic_keyval_set = TRUE;
-
- g_object_unref (label);
- }
-
- mod = gtk_accelerator_get_default_mod_mask () & state;
-
- /* Handle info button mnemonic */
- if ((mod & GDK_MOD1_MASK) != 0 &&
- keyval == mnemonic_keyval &&
- gtk_widget_get_sensitive (GTK_WIDGET (completion->info_button)))
- {
- gtk_toggle_button_set_active (completion->info_button,
- !gtk_toggle_button_get_active (completion->info_button));
- return TRUE;
- }
-
- if ((mod & GDK_MOD1_MASK) != 0 &&
- GDK_KEY_0 <= keyval &&
- keyval <= GDK_KEY_9)
- {
- if (activate_by_accelerator (completion, keyval - GDK_KEY_0))
- {
- return TRUE;
- }
- }
-
- binding_set = gtk_binding_set_by_class (G_OBJECT_GET_CLASS (completion));
-
- if (gtk_binding_set_activate (binding_set, keyval, state, G_OBJECT (completion)))
- {
- return TRUE;
- }
-#endif
-
- return FALSE;
-}
-
-static void
-buffer_mark_set_cb (GtkTextBuffer *buffer,
- GtkTextIter *iter,
- GtkTextMark *mark,
- GtkSourceCompletion *completion)
-{
- if (mark == gtk_text_buffer_get_insert (buffer))
- {
- gtk_source_completion_hide (completion);
- }
-}
-
-static void
-update_transient_for_info (GObject *window,
- GParamSpec *spec,
- GtkSourceCompletion *completion)
-{
- gtk_window_set_transient_for (GTK_WINDOW (completion->info_window),
- gtk_window_get_transient_for (GTK_WINDOW (completion->main_window)));
-}
-
-static void
-replace_model (GtkSourceCompletion *completion)
-{
- if (completion->model_proposals != NULL)
- {
- g_object_unref (completion->model_proposals);
- }
-
- completion->model_proposals = gtk_source_completion_model_new ();
-
- gtk_source_completion_model_set_show_headers (completion->model_proposals,
- completion->show_headers);
-}
-
-/* Takes ownership of @providers and @context. */
-static void
-update_completion (GtkSourceCompletion *completion,
- GList *providers,
- GtkSourceCompletionContext *context)
-{
- GList *item;
- GtkTextIter context_iter;
- gboolean valid_context;
-
- /* Copy the parameters, because they can be freed by reset_completion(). */
- GList *providers_copy = g_list_copy (providers);
- GtkSourceCompletionContext *context_copy = g_object_ref_sink (context);
-
- /* Make sure to first cancel any running completion */
- reset_completion (completion);
-
- completion->context = context_copy;
- completion->running_providers = g_list_copy (providers_copy);
- completion->active_providers = g_list_copy (providers_copy);
-
- /* Create a new CompletionModel */
- gtk_tree_view_set_model (completion->tree_view_proposals, NULL);
- gtk_tree_view_columns_autosize (completion->tree_view_proposals);
-
- replace_model (completion);
-
- valid_context = gtk_source_completion_context_get_iter (context_copy, &context_iter);
-
- if (valid_context)
- {
- for (item = providers_copy; item != NULL; item = g_list_next (item))
- {
- GtkSourceCompletionProvider *provider = item->data;
- gtk_source_completion_provider_populate (provider, context_copy);
- }
- }
-
- g_list_free (providers_copy);
-}
-
-static gboolean
-auto_completion_final (GtkSourceCompletion *completion)
-{
- /* Store and set to NULL because update_completion will cancel the last
- completion, which will also remove the timeout source which in turn
- would free these guys */
- GtkSourceCompletionContext *context = completion->auto_completion_context;
- GList *selection = completion->auto_completion_selection;
-
- completion->auto_completion_context = NULL;
- completion->auto_completion_selection = NULL;
-
- update_completion (completion, selection, context);
-
- g_list_free (selection);
- g_object_unref (context);
- return G_SOURCE_REMOVE;
-}
-
-static void
-auto_completion_destroy (GtkSourceCompletion *completion)
-{
- if (completion->auto_completion_context != NULL)
- {
- g_object_unref (completion->auto_completion_context);
- completion->auto_completion_context = NULL;
- }
-
- g_list_free (completion->auto_completion_selection);
- completion->auto_completion_selection = NULL;
-}
-
-static void
-start_interactive_completion (GtkSourceCompletion *completion,
- GtkTextIter *iter)
-{
- GtkSourceCompletionContext *context;
- GList *providers;
- gint delay;
-
- reset_completion (completion);
-
- /* Create the context */
- context = gtk_source_completion_create_context (completion, iter);
- g_object_ref_sink (context);
-
- g_object_set (context,
- "activation",
- GTK_SOURCE_COMPLETION_ACTIVATION_INTERACTIVE,
- NULL);
-
- g_signal_emit (completion, signals[POPULATE_CONTEXT], 0, context);
-
- /* Select providers */
- providers = select_providers (completion->providers, context);
-
- if (providers == NULL)
- {
- g_object_unref (context);
- return;
- }
-
- /* Create the timeout */
- delay = minimum_auto_complete_delay (completion, providers);
- completion->auto_completion_context = context;
- completion->auto_completion_selection = providers;
-
- completion->show_timed_out_id =
- g_timeout_add_full (G_PRIORITY_DEFAULT,
- delay,
- (GSourceFunc)auto_completion_final,
- completion,
- (GDestroyNotify)auto_completion_destroy);
-}
-
-static void
-update_active_completion (GtkSourceCompletion *completion,
- GtkTextIter *new_iter)
-{
- GList *selected_providers;
-
- g_assert (completion->context != NULL);
-
- g_object_set (completion->context,
- "iter", new_iter,
- NULL);
-
- selected_providers = select_providers (completion->providers,
- completion->context);
-
- if (selected_providers != NULL)
- {
- update_completion (completion,
- selected_providers,
- completion->context);
-
- g_list_free (selected_providers);
- }
- else
- {
- gtk_source_completion_hide (completion);
- }
-}
-
-static void
-buffer_delete_range_cb (GtkTextBuffer *buffer,
- GtkTextIter *start,
- GtkTextIter *end,
- GtkSourceCompletion *completion)
-{
- if (completion->context != NULL)
- {
- update_active_completion (completion, start);
- }
-}
-
-static void
-buffer_insert_text_cb (GtkTextBuffer *buffer,
- GtkTextIter *location,
- gchar *text,
- gint len,
- GtkSourceCompletion *completion)
-{
- if (completion->context != NULL)
- {
- update_active_completion (completion, location);
- }
- else
- {
- start_interactive_completion (completion, location);
- }
-}
-
-static void
-update_bottom_bar_visibility (GtkSourceCompletion *completion)
-{
- GList *providers;
- guint nb_providers;
-
- providers = gtk_source_completion_model_get_providers (completion->model_proposals);
- nb_providers = g_list_length (providers);
- g_list_free (providers);
-
- if (nb_providers > 1)
- {
- gtk_widget_show (completion->bottom_bar);
- return;
- }
-
- if (gtk_source_completion_model_has_info (completion->model_proposals))
- {
- gtk_widget_show (completion->bottom_bar);
- }
- else
- {
- gtk_widget_hide (completion->bottom_bar);
- }
-}
-
-static void
-style_context_changed (GtkStyleContext *style_context,
- GtkSourceCompletion *completion)
-{
- PangoFontDescription *font_desc;
- PangoContext *context;
-
- if (completion->view == NULL)
- {
- return;
- }
-
- context = gtk_widget_get_pango_context (GTK_WIDGET (completion->view));
- font_desc = pango_context_get_font_description (context);
-
- /*
- * Work around issue where when a proposal provides "<b>markup</b>" and
- * the weight is set in the font description, the <b> markup will not
- * have it's weight respected. This seems to be happening because the
- * weight mask is getting set in pango_font_description_from_string()
- * even if the the value is set to normal. That matter is complicated
- * because PangoAttrFontDesc and PangoAttrWeight will both have the
- * same starting offset in the PangoLayout.
- * https://bugzilla.gnome.org/show_bug.cgi?id=755968
- */
- if (PANGO_WEIGHT_NORMAL == pango_font_description_get_weight (font_desc))
- {
- font_desc = pango_font_description_copy (font_desc);
- pango_font_description_unset_fields (font_desc, PANGO_FONT_MASK_WEIGHT);
- g_object_set (completion->cell_renderer_proposal,
- "font-desc", font_desc,
- NULL);
- pango_font_description_free (font_desc);
- }
- else
- {
- g_object_set (completion->cell_renderer_proposal,
- "font-desc", font_desc,
- NULL);
- }
-}
-
-static void
-populating_done (GtkSourceCompletion *completion,
- GtkSourceCompletionContext *context)
-{
- if (gtk_source_completion_model_is_empty (completion->model_proposals, TRUE))
- {
- gtk_source_completion_hide (completion);
- return;
- }
-
- gtk_tree_view_set_model (completion->tree_view_proposals,
- GTK_TREE_MODEL (completion->model_proposals));
-
- update_selection_label (completion);
- update_bottom_bar_visibility (completion);
-
- if (!check_first_selected (completion))
- {
- /* Update the window position only if the first proposal is not
- * selected, because if it is selected, the window position will
- * already be updated.
- */
- update_window_position (completion);
- }
-
- if (!gtk_widget_get_visible (GTK_WIDGET (completion->main_window)))
- {
- g_signal_emit (completion, signals[SHOW], 0);
- }
-}
-
-static void
-gtk_source_completion_dispose (GObject *object)
-{
- GtkSourceCompletion *completion = GTK_SOURCE_COMPLETION (object);
-
- reset_completion (completion);
-
- if (completion->view != NULL)
- {
- g_object_remove_weak_pointer (G_OBJECT (completion->view),
- (gpointer *)&completion->view);
-
- completion->view = NULL;
- }
-
- g_clear_object (&completion->buffer);
- g_clear_object (&completion->default_info);
- g_clear_object (&completion->model_proposals);
-
- if (completion->info_window != NULL)
- {
- gtk_window_destroy (GTK_WINDOW (completion->info_window));
- completion->info_window = NULL;
- }
-
- if (completion->main_window != NULL)
- {
- gtk_window_destroy (GTK_WINDOW (completion->main_window));
- completion->main_window = NULL;
- }
-
- g_list_free_full (completion->providers, g_object_unref);
- completion->providers = NULL;
-
- G_OBJECT_CLASS (gtk_source_completion_parent_class)->dispose (object);
-}
-
-/* Unconditionnally block interactive completion, without taking into account
- * priv->block_interactive_num.
- * g_signal_handlers_block_by_func() has a counter too, so you may think that
- * block_interactive_num is useless. But it is useful when the buffer changes,
- * to keep the signal handler blocked on the new buffer.
- */
-static void
-block_interactive (GtkSourceCompletion *completion)
-{
- g_signal_handlers_block_by_func (completion->buffer,
- buffer_insert_text_cb,
- completion);
-
- g_signal_handlers_block_by_func (completion->buffer,
- buffer_delete_range_cb,
- completion);
-}
-
-static void
-connect_buffer (GtkSourceCompletion *completion)
-{
- GtkTextBuffer *new_buffer = NULL;
-
- if (completion->view != NULL)
- {
- new_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (completion->view));
- }
-
- if (completion->buffer == new_buffer)
- {
- return;
- }
-
- if (completion->buffer != NULL)
- {
- g_signal_handlers_disconnect_by_func (completion->buffer,
- buffer_mark_set_cb,
- completion);
-
- g_signal_handlers_disconnect_by_func (completion->buffer,
- gtk_source_completion_block_interactive,
- completion);
-
- g_signal_handlers_disconnect_by_func (completion->buffer,
- gtk_source_completion_unblock_interactive,
- completion);
-
- g_signal_handlers_disconnect_by_func (completion->buffer,
- buffer_delete_range_cb,
- completion);
-
- g_signal_handlers_disconnect_by_func (completion->buffer,
- buffer_insert_text_cb,
- completion);
-
- reset_completion (completion);
-
- g_object_unref (completion->buffer);
- }
-
- completion->buffer = new_buffer;
-
- if (new_buffer == NULL)
- {
- return;
- }
-
- g_object_ref (completion->buffer);
-
- g_signal_connect_object (new_buffer,
- "mark-set",
- G_CALLBACK (buffer_mark_set_cb),
- completion,
- G_CONNECT_AFTER);
-
- g_signal_connect_object (new_buffer,
- "undo",
- G_CALLBACK (gtk_source_completion_block_interactive),
- completion,
- G_CONNECT_SWAPPED);
-
- g_signal_connect_object (new_buffer,
- "undo",
- G_CALLBACK (gtk_source_completion_unblock_interactive),
- completion,
- G_CONNECT_SWAPPED | G_CONNECT_AFTER);
-
- g_signal_connect_object (new_buffer,
- "redo",
- G_CALLBACK (gtk_source_completion_block_interactive),
- completion,
- G_CONNECT_SWAPPED);
-
- g_signal_connect_object (new_buffer,
- "redo",
- G_CALLBACK (gtk_source_completion_unblock_interactive),
- completion,
- G_CONNECT_SWAPPED | G_CONNECT_AFTER);
-
- g_signal_connect_object (new_buffer,
- "delete-range",
- G_CALLBACK (buffer_delete_range_cb),
- completion,
- G_CONNECT_AFTER);
-
- g_signal_connect_object (new_buffer,
- "insert-text",
- G_CALLBACK (buffer_insert_text_cb),
- completion,
- G_CONNECT_AFTER);
-
- if (completion->block_interactive_num > 0)
- {
- block_interactive (completion);
- }
-}
-
-static void
-connect_view (GtkSourceCompletion *completion,
- GtkSourceView *view)
-{
- GtkEventController *key;
- GtkEventController *focus;
- GtkGesture *click;
-
- g_assert (completion->view == NULL);
- completion->view = view;
-
- key = gtk_event_controller_key_new ();
- gtk_widget_add_controller (GTK_WIDGET (view), key);
-
- click = gtk_gesture_click_new ();
- gtk_widget_add_controller (GTK_WIDGET (view), GTK_EVENT_CONTROLLER (click));
-
- focus = gtk_event_controller_focus_new ();
- gtk_widget_add_controller (GTK_WIDGET (view), GTK_EVENT_CONTROLLER (focus));
-
- g_object_add_weak_pointer (G_OBJECT (view),
- (gpointer *)&completion->view);
-
- g_signal_connect_object (click,
- "pressed",
- G_CALLBACK (hide_completion_cb),
- completion,
- G_CONNECT_SWAPPED);
-
- g_signal_connect_object (focus,
- "leave",
- G_CALLBACK (hide_completion_cb),
- completion,
- G_CONNECT_SWAPPED);
-
- g_signal_connect_object (key,
- "key-pressed",
- G_CALLBACK (view_key_press_event_cb),
- completion,
- 0);
-
- g_signal_connect_object (completion->view,
- "paste-clipboard",
- G_CALLBACK (gtk_source_completion_block_interactive),
- completion,
- G_CONNECT_SWAPPED);
-
- g_signal_connect_object (completion->view,
- "paste-clipboard",
- G_CALLBACK (gtk_source_completion_unblock_interactive),
- completion,
- G_CONNECT_SWAPPED | G_CONNECT_AFTER);
-
- connect_buffer (completion);
-
- g_signal_connect_object (completion->view,
- "notify::buffer",
- G_CALLBACK (connect_buffer),
- completion,
- G_CONNECT_SWAPPED);
-}
-
-static void
-gtk_source_completion_get_property (GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
-{
- GtkSourceCompletion *completion;
-
- g_return_if_fail (GTK_SOURCE_IS_COMPLETION (object));
-
- completion = GTK_SOURCE_COMPLETION (object);
-
- switch (prop_id)
- {
- case PROP_VIEW:
- g_value_set_object (value, completion->view);
- break;
- case PROP_REMEMBER_INFO_VISIBILITY:
- g_value_set_boolean (value, completion->remember_info_visibility);
- break;
- case PROP_SELECT_ON_SHOW:
- g_value_set_boolean (value, completion->select_on_show);
- break;
- case PROP_SHOW_HEADERS:
- g_value_set_boolean (value, completion->show_headers);
- break;
- case PROP_SHOW_ICONS:
- g_value_set_boolean (value, completion->show_icons);
- break;
- case PROP_ACCELERATORS:
- g_value_set_uint (value, completion->num_accelerators);
- break;
- case PROP_AUTO_COMPLETE_DELAY:
- g_value_set_uint (value, completion->auto_complete_delay);
- break;
- case PROP_PROPOSAL_PAGE_SIZE:
- g_value_set_uint (value, completion->proposal_page_size);
- break;
- case PROP_PROVIDER_PAGE_SIZE:
- g_value_set_uint (value, completion->provider_page_size);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
-
-static void
-gtk_source_completion_set_property (GObject *object,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- GtkSourceCompletion *completion;
-
- g_return_if_fail (GTK_SOURCE_IS_COMPLETION (object));
-
- completion = GTK_SOURCE_COMPLETION (object);
-
- switch (prop_id)
- {
- case PROP_VIEW:
- connect_view (completion, g_value_get_object (value));
- break;
- case PROP_REMEMBER_INFO_VISIBILITY:
- completion->remember_info_visibility = g_value_get_boolean (value);
- break;
- case PROP_SELECT_ON_SHOW:
- completion->select_on_show = g_value_get_boolean (value);
- break;
- case PROP_SHOW_HEADERS:
- completion->show_headers = g_value_get_boolean (value);
-
- if (completion->model_proposals != NULL)
- {
- gtk_source_completion_model_set_show_headers (completion->model_proposals,
- completion->show_headers);
- }
- break;
- case PROP_SHOW_ICONS:
- completion->show_icons = g_value_get_boolean (value);
- break;
- case PROP_ACCELERATORS:
- completion->num_accelerators = g_value_get_uint (value);
- break;
- case PROP_AUTO_COMPLETE_DELAY:
- completion->auto_complete_delay = g_value_get_uint (value);
- break;
- case PROP_PROPOSAL_PAGE_SIZE:
- completion->proposal_page_size = g_value_get_uint (value);
- break;
- case PROP_PROVIDER_PAGE_SIZE:
- completion->provider_page_size = g_value_get_uint (value);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
-
-static gboolean
-selection_func (GtkTreeSelection *selection,
- GtkTreeModel *model,
- GtkTreePath *path,
- gboolean path_currently_selected,
- GtkSourceCompletion *completion)
-{
- GtkTreeIter iter;
-
- gtk_tree_model_get_iter (model, &iter, path);
-
- if (gtk_source_completion_model_iter_is_header (completion->model_proposals,
- &iter))
- {
- /* A header must never be selected */
- g_return_val_if_fail (!path_currently_selected, TRUE);
- return FALSE;
- }
- else
- {
- return TRUE;
- }
-}
-
-static void
-accelerators_notify_cb (GtkSourceCompletion *completion,
- GParamSpec *pspec,
- GtkTreeViewColumn *column)
-{
- gtk_tree_view_column_set_visible (column, completion->num_accelerators > 0);
-}
-
-static void
-cell_icon_func (GtkTreeViewColumn *column,
- GtkCellRenderer *cell,
- GtkTreeModel *model,
- GtkTreeIter *iter,
- gpointer data)
-{
- GdkTexture *texture;
- gchar *icon_name;
- GIcon *gicon;
- gboolean set = FALSE;
-
- gtk_tree_model_get (model, iter,
- GTK_SOURCE_COMPLETION_MODEL_COLUMN_ICON, &texture,
- GTK_SOURCE_COMPLETION_MODEL_COLUMN_ICON_NAME, &icon_name,
- GTK_SOURCE_COMPLETION_MODEL_COLUMN_GICON, &gicon,
- -1);
-
- if (texture != NULL)
- {
- g_object_set (cell, "texture", texture, NULL);
- g_object_unref (texture);
- set = TRUE;
- }
-
- if (icon_name != NULL)
- {
- g_object_set (cell, "icon-name", icon_name, NULL);
- g_free (icon_name);
- set = TRUE;
- }
-
- if (gicon != NULL)
- {
- g_object_set (cell, "gicon", gicon, NULL);
- g_object_unref (gicon);
- set = TRUE;
- }
-
- if (!set)
- {
- g_object_set (cell, "icon-name", NULL, NULL);
- }
-}
-
-static void
-init_tree_view (GtkSourceCompletion *completion,
- GtkBuilder *builder)
-{
- GtkTreeSelection *selection;
- GtkTreeViewColumn *column;
- GtkCellRenderer *cell_renderer;
- GtkStyleContext *style_context;
- GdkRGBA foreground_color;
-
- completion->insensitive = GTK_TREE_VIEW (gtk_builder_get_object (builder, "insensitive"));
- completion->tree_view_proposals = GTK_TREE_VIEW (gtk_builder_get_object (builder,
"tree_view_proposals"));
-
-#if 0
- g_signal_connect_swapped (completion->tree_view_proposals,
- "row-activated",
- G_CALLBACK (gtk_source_completion_activate_proposal),
- completion);
-
- g_signal_connect_swapped (completion->tree_view_proposals,
- "size-allocate",
- G_CALLBACK (gtk_source_completion_proposals_size_allocate),
- completion);
-#endif
-
- /* Selection */
-
- selection = gtk_tree_view_get_selection (completion->tree_view_proposals);
-
- gtk_tree_selection_set_select_function (selection,
- (GtkTreeSelectionFunc)selection_func,
- completion,
- NULL);
-
- g_signal_connect (selection,
- "changed",
- G_CALLBACK (selection_changed_cb),
- completion);
-
- /* Icon cell renderer */
-
- cell_renderer = GTK_CELL_RENDERER (gtk_builder_get_object (builder, "cell_renderer_icon"));
-
- column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (builder, "tree_view_column_icon"));
-
- /* We use a cell function instead of plain attributes for the icon since
- * the pixbuf renderer will not renderer any icon if pixbuf is set to NULL.
- * See https://bugzilla.gnome.org/show_bug.cgi?id=753510
- */
- gtk_tree_view_column_set_cell_data_func (column,
- cell_renderer,
- cell_icon_func,
- NULL,
- NULL);
-
- gtk_tree_view_column_set_attributes (column, cell_renderer,
- "cell-background-set",
GTK_SOURCE_COMPLETION_MODEL_COLUMN_IS_HEADER,
- NULL);
-
- /* Get styling for insensitive cell background. Ideally this will all
- * go away with a widget'ized form of completion before GTK 4.
- */
- style_context = gtk_widget_get_style_context (GTK_WIDGET (completion->insensitive));
- gtk_style_context_get_color (style_context, &foreground_color);
-
- g_object_bind_property (completion, "show-icons",
- cell_renderer, "visible",
- G_BINDING_SYNC_CREATE);
-
- /* Proposal text cell renderer */
-
- cell_renderer = GTK_CELL_RENDERER (gtk_builder_get_object (builder, "cell_renderer_proposal"));
- completion->cell_renderer_proposal = cell_renderer;
-
- column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (builder, "tree_view_column_proposal"));
-
- gtk_tree_view_column_set_attributes (column, cell_renderer,
- "markup", GTK_SOURCE_COMPLETION_MODEL_COLUMN_MARKUP,
- "cell-background-set",
GTK_SOURCE_COMPLETION_MODEL_COLUMN_IS_HEADER,
- "foreground-set", GTK_SOURCE_COMPLETION_MODEL_COLUMN_IS_HEADER,
- NULL);
-
- g_object_set (cell_renderer,
- "foreground-rgba", &foreground_color,
- NULL);
-
- /* Accelerators cell renderer */
-
- column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (builder, "tree_view_column_accelerator"));
-
- cell_renderer = GTK_CELL_RENDERER (gtk_builder_get_object (builder, "cell_renderer_accelerator"));
-
- g_object_set (cell_renderer,
- "foreground-rgba", &foreground_color,
- NULL);
-
- gtk_tree_view_column_set_cell_data_func (column,
- cell_renderer,
- (GtkTreeCellDataFunc)render_proposal_accelerator_func,
- completion,
- NULL);
+ gtk_source_completion_cancel (self);
+ }
+}
- g_signal_connect_object (completion,
- "notify::accelerators",
- G_CALLBACK (accelerators_notify_cb),
- column,
- 0);
+GtkSourceCompletion *
+_gtk_source_completion_new (GtkSourceView *view)
+{
+ return g_object_new (GTK_SOURCE_TYPE_COMPLETION,
+ "view", view,
+ NULL);
}
static void
-init_main_window (GtkSourceCompletion *completion,
- GtkBuilder *builder)
+gtk_source_completion_set_view (GtkSourceCompletion *self,
+ GtkSourceView *view)
{
- if (completion->view == NULL)
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
+ g_assert (GTK_SOURCE_IS_VIEW (view));
+
+ if (g_set_weak_pointer (&self->view, view))
{
- return;
+ gtk_source_signal_group_set_target (self->view_signals, view);
+ g_object_bind_property (view, "buffer",
+ self->buffer_signals, "target",
+ G_BINDING_SYNC_CREATE);
}
-
- completion->main_window = GTK_SOURCE_COMPLETION_INFO (gtk_builder_get_object (builder,
"main_window"));
- completion->info_button = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "info_button"));
- completion->selection_image = GTK_IMAGE (gtk_builder_get_object (builder, "selection_image"));
- completion->selection_label = GTK_LABEL (gtk_builder_get_object (builder, "selection_label"));
- completion->bottom_bar = GTK_WIDGET (gtk_builder_get_object (builder, "bottom_bar"));
-
- g_object_set (completion->main_window,
- "margin-top", 0,
- "margin-bottom", 0,
- "margin-start", 0,
- "margin-end", 0,
- NULL);
-
-#if 0
- g_signal_connect_swapped (completion->main_window,
- "size-allocate",
- G_CALLBACK (update_window_position),
- completion);
-#endif
-
- gtk_window_set_hide_on_close (GTK_WINDOW (completion->main_window),
- TRUE);
-
- g_signal_connect (completion->main_window,
- "notify::transient-for",
- G_CALLBACK (update_transient_for_info),
- completion);
-
- g_signal_connect_swapped (completion->info_button,
- "toggled",
- G_CALLBACK (update_info_window_visibility),
- completion);
}
static void
-init_info_window (GtkSourceCompletion *completion)
+on_buffer_signals_bind (GtkSourceCompletion *self,
+ GtkSourceBuffer *buffer,
+ GtkSourceSignalGroup *signals_)
{
- completion->info_window = gtk_source_completion_info_new ();
- g_object_ref_sink (completion->info_window);
+ GtkTextIter where;
-#if 0
- g_signal_connect_swapped (completion->info_window,
- "size-allocate",
- G_CALLBACK (update_info_position),
- completion);
-#endif
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
+ g_assert (GTK_SOURCE_IS_BUFFER (buffer));
+ g_assert (GTK_SOURCE_IS_SIGNAL_GROUP (signals_));
- /* Default info widget */
+ if (self->disposed)
+ return;
- completion->default_info = GTK_LABEL (gtk_label_new (NULL));
- g_object_ref_sink (completion->default_info);
+ gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &where);
+ self->completion_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (buffer),
+ NULL,
+ &where,
+ TRUE);
- gtk_widget_show (GTK_WIDGET (completion->default_info));
+ if (self->display != NULL)
+ {
+ _gtk_source_assistant_set_mark (GTK_SOURCE_ASSISTANT (self->display),
+ self->completion_mark);
+ }
}
-void
-_gtk_source_completion_css_changed (GtkSourceCompletion *completion,
- GtkCssStyleChange *change)
+static void
+gtk_source_completion_dispose (GObject *object)
{
- GtkStyleContext *style_context;
+ GtkSourceCompletion *self = (GtkSourceCompletion *)object;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION (self));
- g_assert (GTK_SOURCE_IS_COMPLETION (completion));
+ self->disposed = TRUE;
- if (completion->view == NULL)
+ gtk_source_signal_group_set_target (self->context_signals, NULL);
+ gtk_source_signal_group_set_target (self->buffer_signals, NULL);
+ gtk_source_signal_group_set_target (self->view_signals, NULL);
+
+ if (self->display != NULL)
{
- return;
+ _gtk_source_assistant_destroy (GTK_SOURCE_ASSISTANT (self->display));
+ self->display = NULL;
}
- style_context = gtk_widget_get_style_context (GTK_WIDGET (completion->view));
+ g_clear_object (&self->context);
+ g_clear_object (&self->cancellable);
+
+ if (self->providers->len > 0)
+ {
+ g_ptr_array_remove_range (self->providers, 0, self->providers->len);
+ }
- style_context_changed (style_context, completion);
+ G_OBJECT_CLASS (gtk_source_completion_parent_class)->dispose (object);
}
static void
-gtk_source_completion_constructed (GObject *object)
+gtk_source_completion_finalize (GObject *object)
{
- GtkSourceCompletion *completion = GTK_SOURCE_COMPLETION (object);
- GError *error = NULL;
- GtkBuilder *builder = gtk_builder_new ();
+ GtkSourceCompletion *self = (GtkSourceCompletion *)object;
+
+ g_clear_weak_pointer (&self->view);
- gtk_builder_set_translation_domain (builder, GETTEXT_PACKAGE);
+ G_OBJECT_CLASS (gtk_source_completion_parent_class)->finalize (object);
+}
- gtk_builder_add_from_resource (builder,
- "/org/gnome/gtksourceview/ui/gtksourcecompletion.ui",
- &error);
+static void
+gtk_source_completion_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkSourceCompletion *self = GTK_SOURCE_COMPLETION (object);
- if (error != NULL)
+ switch (prop_id)
{
- g_error ("Error while loading the completion UI: %s", error->message);
+ case PROP_SELECT_ON_SHOW:
+ g_value_set_boolean (value, _gtk_source_completion_get_select_on_show (self));
+ break;
+
+ case PROP_VIEW:
+ g_value_set_object (value, self->view);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
+}
- init_tree_view (completion, builder);
- init_main_window (completion, builder);
- init_info_window (completion);
+static void
+gtk_source_completion_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkSourceCompletion *self = GTK_SOURCE_COMPLETION (object);
- _gtk_source_completion_css_changed (completion, NULL);
+ switch (prop_id)
+ {
+ case PROP_SELECT_ON_SHOW:
+ gtk_source_completion_set_select_on_show (self, g_value_get_boolean (value));
+ break;
- g_object_unref (builder);
+ case PROP_VIEW:
+ gtk_source_completion_set_view (self, g_value_get_object (value));
+ break;
- G_OBJECT_CLASS (gtk_source_completion_parent_class)->constructed (object);
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
}
static void
@@ -2232,817 +906,623 @@ gtk_source_completion_class_init (GtkSourceCompletionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->dispose = gtk_source_completion_dispose;
+ object_class->finalize = gtk_source_completion_finalize;
object_class->get_property = gtk_source_completion_get_property;
object_class->set_property = gtk_source_completion_set_property;
- object_class->dispose = gtk_source_completion_dispose;
- object_class->constructed = gtk_source_completion_constructed;
/**
- * GtkSourceCompletion:view:
+ * GtkSourceCompletion:buffer:
+ *
+ * The #GtkTextBuffer for the #GtkSourceCompletion:view.
+ * This is a convenience property for providers.
*
- * The #GtkSourceView bound to the completion object.
+ * Since: 5.0
*/
- g_object_class_install_property (object_class,
- PROP_VIEW,
- g_param_spec_object ("view",
- "View",
- "The GtkSourceView bound to the completion",
- GTK_SOURCE_TYPE_VIEW,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT_ONLY |
- G_PARAM_STATIC_STRINGS));
+ properties [PROP_BUFFER] =
+ g_param_spec_object ("buffer",
+ "Buffer",
+ "The buffer for the view",
+ GTK_TYPE_TEXT_VIEW,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
/**
- * GtkSourceCompletion:remember-info-visibility:
+ * GtkSourceCompletion:page-size:
*
- * Determines whether the visibility of the info window should be
- * saved when the completion is hidden, and restored when the completion
- * is shown again.
- */
- g_object_class_install_property (object_class,
- PROP_REMEMBER_INFO_VISIBILITY,
- g_param_spec_boolean ("remember-info-visibility",
- "Remember Info Visibility",
- "Remember the last info window visibility
state",
- FALSE,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT |
- G_PARAM_STATIC_STRINGS));
- /**
- * GtkSourceCompletion:select-on-show:
+ * The number of rows to display to the user before scrolling.
*
- * Determines whether the first proposal should be selected when the
- * completion is first shown.
+ * Since: 5.0
*/
- g_object_class_install_property (object_class,
- PROP_SELECT_ON_SHOW,
- g_param_spec_boolean ("select-on-show",
- "Select on Show",
- "Select first proposal when completion is
shown",
- TRUE,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT |
- G_PARAM_STATIC_STRINGS));
+ properties [PROP_PAGE_SIZE] =
+ g_param_spec_uint ("page-size",
+ "Number of Rows",
+ "Number of rows to display to the user",
+ 1, 32, DEFAULT_PAGE_SIZE,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
- * GtkSourceCompletion:show-headers:
+ * GtkSourceCompletion:select-on-show:
*
- * Determines whether provider headers should be shown in the proposal
- * list. It can be useful to disable when there is only one provider.
+ * Determines whether the first proposal should be selected when the completion
+ * is first shown.
*/
- g_object_class_install_property (object_class,
- PROP_SHOW_HEADERS,
- g_param_spec_boolean ("show-headers",
- "Show Headers",
- "Show provider headers when proposals from
multiple providers are available",
- TRUE,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT |
- G_PARAM_STATIC_STRINGS));
+ properties [PROP_SELECT_ON_SHOW] =
+ g_param_spec_boolean ("select-on-show",
+ "Select on Show",
+ "Select on Show",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
- * GtkSourceCompletion:show-icons:
+ * GtkSourceCompletion:view:
*
- * Determines whether provider and proposal icons should be shown in
- * the completion popup.
- */
- g_object_class_install_property (object_class,
- PROP_SHOW_ICONS,
- g_param_spec_boolean ("show-icons",
- "Show Icons",
- "Show provider and proposal icons in the
completion popup",
- TRUE,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT |
- G_PARAM_STATIC_STRINGS));
-
- /**
- * GtkSourceCompletion:accelerators:
+ * The "view" property is the #GtkTextView for which this #GtkSourceCompletion
+ * is providing completion features.
*
- * Number of keyboard accelerators to show for the first proposals. For
- * example, to activate the first proposal, the user can press
- * <keycombo><keycap>Alt</keycap><keycap>1</keycap></keycombo>.
+ * Since: 5.0
*/
- g_object_class_install_property (object_class,
- PROP_ACCELERATORS,
- g_param_spec_uint ("accelerators",
- "Accelerators",
- "Number of proposal accelerators to show",
- 0,
- 10,
- 5,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT |
- G_PARAM_STATIC_STRINGS));
+ properties [PROP_VIEW] =
+ g_param_spec_object ("view",
+ "View",
+ "The text view for which to provide completion",
+ GTK_SOURCE_TYPE_VIEW,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
/**
- * GtkSourceCompletion:auto-complete-delay:
+ * GtkSourceCompletion::provider-added:
+ * @self: an #ideCompletion
+ * @provider: an #GtkSourceCompletionProvider
+ *
+ * The "provided-added" signal is emitted when a new provider is
+ * added to the completion.
*
- * Determines the popup delay (in milliseconds) at which the completion
- * will be shown for interactive completion.
+ * Since: 5.0
*/
- g_object_class_install_property (object_class,
- PROP_AUTO_COMPLETE_DELAY,
- g_param_spec_uint ("auto-complete-delay",
- "Auto Complete Delay",
- "Completion popup delay for interactive
completion",
- 0,
- G_MAXUINT,
- 250,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT |
- G_PARAM_STATIC_STRINGS));
+ signals [PROVIDER_ADDED] =
+ g_signal_new ("provider-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, GTK_SOURCE_TYPE_COMPLETION_PROVIDER);
+ g_signal_set_va_marshaller (signals [PROVIDER_ADDED],
+ G_TYPE_FROM_CLASS (klass),
+ g_cclosure_marshal_VOID__OBJECTv);
/**
- * GtkSourceCompletion:provider-page-size:
+ * GtkSourceCompletion::provider-removed:
+ * @self: an #ideCompletion
+ * @provider: an #GtkSourceCompletionProvider
*
- * The scroll page size of the provider pages in the completion window.
+ * The "provided-removed" signal is emitted when a provider has
+ * been removed from the completion.
*
- * See the #GtkSourceCompletion::move-page signal.
+ * Since: 5.0
*/
- g_object_class_install_property (object_class,
- PROP_PROVIDER_PAGE_SIZE,
- g_param_spec_uint ("provider-page-size",
- "Provider Page Size",
- "Provider scrolling page size",
- 1,
- G_MAXUINT,
- 5,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT |
- G_PARAM_STATIC_STRINGS));
+ signals [PROVIDER_REMOVED] =
+ g_signal_new ("provider-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, GTK_SOURCE_TYPE_COMPLETION_PROVIDER);
+ g_signal_set_va_marshaller (signals [PROVIDER_REMOVED],
+ G_TYPE_FROM_CLASS (klass),
+ g_cclosure_marshal_VOID__OBJECTv);
/**
- * GtkSourceCompletion:proposal-page-size:
+ * GtkSourceCompletion::hide:
+ * @self: an #GtkSourceCompletion
*
- * The scroll page size of the proposals in the completion window. In
- * other words, when <keycap>PageDown</keycap> or
- * <keycap>PageUp</keycap> is pressed, the selected
- * proposal becomes the one which is located one page size backward or
- * forward.
+ * The "hide" signal is emitted when the completion window should
+ * be hidden.
*
- * See also the #GtkSourceCompletion::move-cursor signal.
+ * Since: 5.0
*/
- g_object_class_install_property (object_class,
- PROP_PROPOSAL_PAGE_SIZE,
- g_param_spec_uint ("proposal-page-size",
- "Proposal Page Size",
- "Proposal scrolling page size",
- 1,
- G_MAXUINT,
- 5,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT |
- G_PARAM_STATIC_STRINGS));
+ signals [HIDE] =
+ g_signal_new_class_handler ("hide",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_CALLBACK (gtk_source_completion_real_hide),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ g_signal_set_va_marshaller (signals [HIDE],
+ G_TYPE_FROM_CLASS (klass),
+ g_cclosure_marshal_VOID__VOIDv);
/**
* GtkSourceCompletion::show:
- * @completion: The #GtkSourceCompletion who emits the signal
+ * @self: an #GtkSourceCompletion
*
- * Emitted when the completion window is shown. The default handler
- * will actually show the window.
+ * The "show" signal is emitted when the completion window should
+ * be shown.
+ *
+ * Since: 5.0
*/
- signals[SHOW] =
+ signals [SHOW] =
g_signal_new_class_handler ("show",
- G_TYPE_FROM_CLASS (klass),
- G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
- G_CALLBACK (gtk_source_completion_show_default),
- NULL, NULL, NULL, G_TYPE_NONE, 0);
-
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_CALLBACK (gtk_source_completion_real_show),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ g_signal_set_va_marshaller (signals [SHOW],
+ G_TYPE_FROM_CLASS (klass),
+ g_cclosure_marshal_VOID__VOIDv);
+}
- /**
- * GtkSourceCompletion::hide:
- * @completion: The #GtkSourceCompletion who emits the signal
- *
- * Emitted when the completion window is hidden. The default handler
- * will actually hide the window.
- */
- signals[HIDE] =
- g_signal_new_class_handler ("hide",
- G_TYPE_FROM_CLASS (klass),
- G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
- G_CALLBACK (gtk_source_completion_hide_default),
- NULL, NULL, NULL, G_TYPE_NONE, 0);
+static void
+gtk_source_completion_init (GtkSourceCompletion *self)
+{
+ self->cancellable = g_cancellable_new ();
+ self->providers = g_ptr_array_new_with_free_func (g_object_unref);
+ self->buffer_signals = gtk_source_signal_group_new (GTK_TYPE_TEXT_BUFFER);
+ self->context_signals = gtk_source_signal_group_new (GTK_SOURCE_TYPE_COMPLETION_CONTEXT);
+ self->view_signals = gtk_source_signal_group_new (GTK_SOURCE_TYPE_VIEW);
+ self->page_size = DEFAULT_PAGE_SIZE;
- /**
- * GtkSourceCompletion::populate-context:
- * @completion: The #GtkSourceCompletion who emits the signal
- * @context: The #GtkSourceCompletionContext for the current completion
- *
- * Emitted just before starting to populate the completion with providers.
- * You can use this signal to add additional attributes in the context.
+ /*
+ * We want to be notified when the context switches from no results to
+ * having results (or vice-versa, when we've filtered to the point of
+ * no results).
*/
- signals[POPULATE_CONTEXT] =
- g_signal_new_class_handler ("populate-context",
- G_TYPE_FROM_CLASS (klass),
- G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
- NULL,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 1,
- GTK_SOURCE_TYPE_COMPLETION_CONTEXT);
-
- /* Actions */
+ gtk_source_signal_group_connect_object (self->context_signals,
+ "notify::empty",
+ G_CALLBACK (gtk_source_completion_notify_context_empty_cb),
+ self,
+ G_CONNECT_SWAPPED);
- /**
- * GtkSourceCompletion::move-cursor:
- * @completion: The #GtkSourceCompletion who emits the signal
- * @step: The #GtkScrollStep by which to move the cursor
- * @num: The amount of steps to move the cursor
- *
- * The #GtkSourceCompletion::move-cursor signal is a keybinding
- * signal which gets emitted when the user initiates a cursor
- * movement.
- *
- * The <keycap>Up</keycap>, <keycap>Down</keycap>,
- * <keycap>PageUp</keycap>, <keycap>PageDown</keycap>,
- * <keycap>Home</keycap> and <keycap>End</keycap> keys are bound to the
- * normal behavior expected by those keys.
- *
- * When @step is equal to %GTK_SCROLL_PAGES, the page size is defined by
- * the #GtkSourceCompletion:proposal-page-size property. It is used for
- * the <keycap>PageDown</keycap> and <keycap>PageUp</keycap> keys.
- *
- * Applications should not connect to it, but may emit it with
- * g_signal_emit_by_name() if they need to control the cursor
- * programmatically.
+ /*
+ * We need to know when the buffer inserts or deletes text so that we
+ * possibly start showing the results, or update our previous completion
+ * request.
*/
- signals[MOVE_CURSOR] =
- g_signal_new_class_handler ("move-cursor",
- G_TYPE_FROM_CLASS (klass),
- G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
- G_CALLBACK (gtk_source_completion_move_cursor),
- NULL, NULL,
- _gtk_source_marshal_VOID__ENUM_INT,
- G_TYPE_NONE,
- 2,
- GTK_TYPE_SCROLL_STEP,
- G_TYPE_INT);
- g_signal_set_va_marshaller (signals [MOVE_CURSOR],
- G_TYPE_FROM_CLASS (klass),
- _gtk_source_marshal_VOID__ENUM_INTv);
+ g_signal_connect_object (self->buffer_signals,
+ "bind",
+ G_CALLBACK (on_buffer_signals_bind),
+ self,
+ G_CONNECT_SWAPPED);
+ gtk_source_signal_group_connect_object (self->buffer_signals,
+ "delete-range",
+ G_CALLBACK
(gtk_source_completion_buffer_delete_range_after_cb),
+ self,
+ G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+ gtk_source_signal_group_connect_object (self->buffer_signals,
+ "insert-text",
+ G_CALLBACK
(gtk_source_completion_buffer_insert_text_after_cb),
+ self,
+ G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+ gtk_source_signal_group_connect_object (self->buffer_signals,
+ "mark-set",
+ G_CALLBACK (gtk_source_completion_buffer_mark_set_cb),
+ self,
+ G_CONNECT_SWAPPED);
- /**
- * GtkSourceCompletion::move-page:
- * @completion: The #GtkSourceCompletion who emits the signal
- * @step: The #GtkScrollStep by which to move the page
- * @num: The amount of steps to move the page
- *
- * The #GtkSourceCompletion::move-page signal is a keybinding
- * signal which gets emitted when the user initiates a page
- * movement (i.e. switches between provider pages).
- *
- * <keycombo><keycap>Control</keycap><keycap>Left</keycap></keycombo>
- * is for going to the previous provider.
- * <keycombo><keycap>Control</keycap><keycap>Right</keycap></keycombo>
- * is for going to the next provider.
- * <keycombo><keycap>Control</keycap><keycap>Home</keycap></keycombo>
- * is for displaying all the providers.
- * <keycombo><keycap>Control</keycap><keycap>End</keycap></keycombo>
- * is for going to the last provider.
- *
- * When @step is equal to #GTK_SCROLL_PAGES, the page size is defined by
- * the #GtkSourceCompletion:provider-page-size property.
- *
- * Applications should not connect to it, but may emit it with
- * g_signal_emit_by_name() if they need to control the page selection
- * programmatically.
+ /*
+ * We track some events on the view that owns our GtkSourceCompletion instance so
+ * that we can hide the window when it definitely should not be displayed.
*/
- signals[MOVE_PAGE] =
- g_signal_new_class_handler ("move-page",
- G_TYPE_FROM_CLASS (klass),
- G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
- G_CALLBACK (gtk_source_completion_move_page),
- NULL, NULL,
- _gtk_source_marshal_VOID__ENUM_INT,
- G_TYPE_NONE,
- 2,
- GTK_TYPE_SCROLL_STEP,
- G_TYPE_INT);
- g_signal_set_va_marshaller (signals [MOVE_PAGE],
- G_TYPE_FROM_CLASS (klass),
- _gtk_source_marshal_VOID__ENUM_INTv);
+ gtk_source_signal_group_connect_object (self->view_signals,
+ "move-cursor",
+ G_CALLBACK (gtk_source_completion_view_move_cursor_cb),
+ self,
+ G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+ gtk_source_signal_group_connect_object (self->view_signals,
+ "paste-clipboard",
+ G_CALLBACK (gtk_source_completion_block_interactive),
+ self,
+ G_CONNECT_SWAPPED);
+ gtk_source_signal_group_connect_object (self->view_signals,
+ "paste-clipboard",
+ G_CALLBACK (gtk_source_completion_unblock_interactive),
+ self,
+ G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+}
- /**
- * GtkSourceCompletion::activate-proposal:
- * @completion: The #GtkSourceCompletion who emits the signal
- *
- * The #GtkSourceCompletion::activate-proposal signal is a
- * keybinding signal which gets emitted when the user initiates
- * a proposal activation.
- *
- * Applications should not connect to it, but may emit it with
- * g_signal_emit_by_name() if they need to control the proposal
- * activation programmatically.
- */
- signals[ACTIVATE_PROPOSAL] =
- g_signal_new_class_handler ("activate-proposal",
- G_TYPE_FROM_CLASS (klass),
- G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
- G_CALLBACK (gtk_source_completion_activate_proposal),
- NULL, NULL, NULL, G_TYPE_NONE, 0);
-
-#if 0
-
- /* XXX: We cannot do keybindings with GObject's any more. This will
- * need to be redesigned to keep events happening either from the
- * completion info class or from the sourceview itself.
- */
+/**
+ * gtk_source_completion_get_view:
+ * @self: a #GtkSourceCompletion
+ *
+ * Gets the #GtkSourceView that owns the #GtkSourceCompletion.
+ *
+ * Returns: (transfer none): A #GtkSourceView
+ *
+ * Since: 5.0
+ */
+GtkSourceView *
+gtk_source_completion_get_view (GtkSourceCompletion *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (self), NULL);
- /* Key bindings */
- binding_set = gtk_binding_set_by_class (klass);
-
- gtk_binding_entry_add_signal (binding_set,
- GDK_KEY_Down,
- 0,
- "move-cursor",
- 2,
- GTK_TYPE_SCROLL_STEP, GTK_SCROLL_STEPS,
- G_TYPE_INT, 1);
-
- gtk_binding_entry_add_signal (binding_set,
- GDK_KEY_Page_Down,
- 0,
- "move-cursor",
- 2,
- GTK_TYPE_SCROLL_STEP, GTK_SCROLL_PAGES,
- G_TYPE_INT, 1);
-
- gtk_binding_entry_add_signal (binding_set,
- GDK_KEY_Up,
- 0,
- "move-cursor",
- 2,
- GTK_TYPE_SCROLL_STEP, GTK_SCROLL_STEPS,
- G_TYPE_INT, -1);
-
- gtk_binding_entry_add_signal (binding_set,
- GDK_KEY_Page_Up,
- 0,
- "move-cursor",
- 2,
- GTK_TYPE_SCROLL_STEP, GTK_SCROLL_PAGES,
- G_TYPE_INT, -1);
-
- gtk_binding_entry_add_signal (binding_set,
- GDK_KEY_Home,
- 0,
- "move-cursor",
- 2,
- GTK_TYPE_SCROLL_STEP, GTK_SCROLL_ENDS,
- G_TYPE_INT, -1);
-
- gtk_binding_entry_add_signal (binding_set,
- GDK_KEY_End,
- 0,
- "move-cursor",
- 2,
- GTK_TYPE_SCROLL_STEP, GTK_SCROLL_ENDS,
- G_TYPE_INT, 1);
-
- gtk_binding_entry_add_signal (binding_set,
- GDK_KEY_Escape,
- 0,
- "hide",
- 0);
-
- gtk_binding_entry_add_signal (binding_set,
- GDK_KEY_Return,
- 0,
- "activate-proposal",
- 0);
-
- gtk_binding_entry_add_signal (binding_set,
- GDK_KEY_Tab,
- 0,
- "activate-proposal",
- 0);
-
- gtk_binding_entry_add_signal (binding_set,
- GDK_KEY_Left,
- GDK_CONTROL_MASK,
- "move-page",
- 2,
- GTK_TYPE_SCROLL_STEP, GTK_SCROLL_STEPS,
- G_TYPE_INT, -1);
-
- gtk_binding_entry_add_signal (binding_set,
- GDK_KEY_Right,
- GDK_CONTROL_MASK,
- "move-page",
- 2,
- GTK_TYPE_SCROLL_STEP, GTK_SCROLL_STEPS,
- G_TYPE_INT, 1);
-
- gtk_binding_entry_add_signal (binding_set,
- GDK_KEY_Home,
- GDK_CONTROL_MASK,
- "move-page",
- 2,
- GTK_TYPE_SCROLL_STEP, GTK_SCROLL_ENDS,
- G_TYPE_INT, -1);
-
- gtk_binding_entry_add_signal (binding_set,
- GDK_KEY_End,
- GDK_CONTROL_MASK,
- "move-page",
- 2,
- GTK_TYPE_SCROLL_STEP, GTK_SCROLL_ENDS,
- G_TYPE_INT, 1);
-#endif
-
- g_type_ensure (GTK_SOURCE_TYPE_COMPLETION_INFO);
+ return self->view;
}
-static void
-gtk_source_completion_init (GtkSourceCompletion *completion)
+/**
+ * gtk_source_completion_get_buffer:
+ * @self: a #GtkSourceCompletion
+ *
+ * Gets the connected #GtkSourceView's #GtkSourceBuffer
+ *
+ * Returns: (transfer none): A #GtkSourceBuffer
+ *
+ * Since: 5.0
+ */
+GtkSourceBuffer *
+gtk_source_completion_get_buffer (GtkSourceCompletion *self)
{
- completion = gtk_source_completion_get_instance_private (completion);
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (self), NULL);
+
+ return GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->view)));
}
+/**
+ * gtk_source_completion_add_provider:
+ * @self: an #GtkSourceCompletion
+ * @provider: an #GtkSourceCompletionProvider
+ *
+ * Adds an #GtkSourceCompletionProvider to the list of providers to be queried
+ * for completion results.
+ *
+ * Since: 5.0
+ */
void
-_gtk_source_completion_add_proposals (GtkSourceCompletion *completion,
- GtkSourceCompletionContext *context,
- GtkSourceCompletionProvider *provider,
- GList *proposals,
- gboolean finished)
+gtk_source_completion_add_provider (GtkSourceCompletion *self,
+ GtkSourceCompletionProvider *provider)
{
- GList *item;
-
- g_return_if_fail (GTK_SOURCE_IS_COMPLETION (completion));
- g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION (self));
g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
- g_return_if_fail (completion->context == context);
-
- item = g_list_find (completion->running_providers, provider);
- g_return_if_fail (item != NULL);
- gtk_source_completion_model_add_proposals (completion->model_proposals,
- provider,
- proposals);
-
- if (finished)
- {
- /* Remove provider from list of running providers */
- completion->running_providers =
- g_list_delete_link (completion->running_providers,
- item);
-
- if (completion->running_providers == NULL)
- {
- populating_done (completion, context);
- }
- }
+ g_ptr_array_add (self->providers, g_object_ref (provider));
+ g_signal_emit (self, signals [PROVIDER_ADDED], 0, provider);
}
/**
- * gtk_source_completion_start:
- * @completion: a #GtkSourceCompletion.
- * @providers: (element-type GtkSource.CompletionProvider) (nullable):
- * a list of #GtkSourceCompletionProvider, or %NULL.
- * @context: (transfer floating): The #GtkSourceCompletionContext
- * with which to start the completion.
- *
- * Starts a new completion with the specified #GtkSourceCompletionContext and
- * a list of potential candidate providers for completion.
- *
- * It can be convenient for showing a completion on-the-fly, without the need to
- * add or remove providers to the #GtkSourceCompletion.
+ * gtk_source_completion_remove_provider:
+ * @self: an #GtkSourceCompletion
+ * @provider: an #GtkSourceCompletionProvider
*
- * Another solution is to add providers with
- * gtk_source_completion_add_provider(), and implement
- * gtk_source_completion_provider_match() for each provider.
+ * Removes an #GtkSourceCompletionProvider previously added with
+ * gtk_source_completion_add_provider().
*
- * Returns: %TRUE if it was possible to the show completion window.
+ * Since: 5.0
*/
-gboolean
-gtk_source_completion_start (GtkSourceCompletion *completion,
- GList *providers,
- GtkSourceCompletionContext *context)
+void
+gtk_source_completion_remove_provider (GtkSourceCompletion *self,
+ GtkSourceCompletionProvider *provider)
{
- GList *selected_providers;
-
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), FALSE);
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), FALSE);
-
- if (completion->view == NULL)
- {
- return FALSE;
- }
+ GtkSourceCompletionProvider *hold = NULL;
- /* Make sure to clear any active completion */
- reset_completion (completion);
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION (self));
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
- /* We need to take owenership of the context right before doing
- anything so we don't leak it or get a crash emitting the signal */
- g_object_ref_sink (context);
+ hold = g_object_ref (provider);
- if (providers == NULL)
+ if (g_ptr_array_remove (self->providers, provider))
{
- g_object_unref (context);
-
- return FALSE;
+ g_signal_emit (self, signals [PROVIDER_REMOVED], 0, hold);
}
- /* Populate the context */
- g_signal_emit (completion, signals[POPULATE_CONTEXT], 0, context);
-
- /* From the providers, select the ones that match the context */
- selected_providers = select_providers (providers, context);
+ g_clear_object (&hold);
+}
- if (selected_providers == NULL)
- {
- g_object_unref (context);
- gtk_source_completion_hide (completion);
- return FALSE;
- }
+/**
+ * gtk_source_completion_show:
+ * @self: an #GtkSourceCompletion
+ *
+ * Emits the "show" signal.
+ *
+ * When the "show" signal is emitted, the completion window will be
+ * displayed if there are any results available.
+ *
+ * Since: 5.0
+ */
+void
+gtk_source_completion_show (GtkSourceCompletion *self)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION (self));
- update_completion (completion, selected_providers, context);
- g_list_free (selected_providers);
- g_object_unref (context);
+ if (gtk_source_completion_is_blocked (self))
+ return;
- return TRUE;
+ self->showing++;
+ if (self->showing == 1)
+ g_signal_emit (self, signals [SHOW], 0);
+ self->showing--;
}
/**
- * gtk_source_completion_get_providers:
- * @completion: a #GtkSourceCompletion.
+ * gtk_source_completion_hide:
+ * @self: an #GtkSourceCompletion
+ *
+ * Emits the "hide" signal.
*
- * Get list of providers registered on @completion. The returned list is owned
- * by the completion and should not be freed.
+ * When the "hide" signal is emitted, the completion window will be
+ * dismissed.
*
- * Returns: (element-type GtkSource.CompletionProvider) (transfer none):
- * list of #GtkSourceCompletionProvider.
+ * Since: 5.0
*/
-GList *
-gtk_source_completion_get_providers (GtkSourceCompletion *completion)
+void
+gtk_source_completion_hide (GtkSourceCompletion *self)
{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), NULL);
- return completion->providers;
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION (self));
+
+ g_signal_emit (self, signals [HIDE], 0);
}
-GQuark
-gtk_source_completion_error_quark (void)
+void
+gtk_source_completion_block_interactive (GtkSourceCompletion *self)
{
- static GQuark quark = 0;
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION (self));
- if (G_UNLIKELY (quark == 0))
- {
- quark = g_quark_from_static_string ("gtk-source-completion-error-quark");
- }
+ self->block_count++;
- return quark;
+ gtk_source_completion_cancel (self);
}
-/**
- * _gtk_source_completion_new:
- * @view: a #GtkSourceView.
- *
- * Creates a new #GtkSourceCompletion associated with @view.
- *
- * Returns: a new #GtkSourceCompletion.
- */
-GtkSourceCompletion *
-_gtk_source_completion_new (GtkSourceView *view)
+void
+gtk_source_completion_unblock_interactive (GtkSourceCompletion *self)
{
- g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), NULL);
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION (self));
- return g_object_new (GTK_SOURCE_TYPE_COMPLETION,
- "view", view,
- NULL);
+ self->block_count--;
}
-/**
- * gtk_source_completion_add_provider:
- * @completion: a #GtkSourceCompletion.
- * @provider: a #GtkSourceCompletionProvider.
- * @error: a #GError.
- *
- * Add a new #GtkSourceCompletionProvider to the completion object. This will
- * add a reference @provider, so make sure to unref your own copy when you
- * no longer need it.
- *
- * Returns: %TRUE if @provider was successfully added, otherwise if @error
- * is provided, it will be set with the error and %FALSE is returned.
- */
-gboolean
-gtk_source_completion_add_provider (GtkSourceCompletion *completion,
- GtkSourceCompletionProvider *provider,
- GError **error)
+void
+gtk_source_completion_set_page_size (GtkSourceCompletion *self,
+ guint page_size)
{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), FALSE);
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider), FALSE);
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION (self));
+ g_return_if_fail (page_size > 0);
+ g_return_if_fail (page_size <= 32);
- if (g_list_find (completion->providers, provider) != NULL)
+ if (self->page_size != page_size)
{
- if (error != NULL)
- {
- g_set_error (error,
- GTK_SOURCE_COMPLETION_ERROR,
- GTK_SOURCE_COMPLETION_ERROR_ALREADY_BOUND,
- "Provider is already bound to this completion object");
- }
-
- return FALSE;
+ self->page_size = page_size;
+ if (self->display != NULL)
+ _gtk_source_completion_list_set_n_rows (self->display, page_size);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PAGE_SIZE]);
}
+}
+
+guint
+gtk_source_completion_get_page_size (GtkSourceCompletion *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (self), 0);
+
+ return self->page_size;
+}
+
+void
+_gtk_source_completion_activate (GtkSourceCompletion *self,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProvider *provider,
+ GtkSourceCompletionProposal *proposal)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION (self));
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal));
+
+ self->block_count++;
+
+ gtk_source_completion_provider_activate (provider, context, proposal);
+ gtk_source_completion_hide (self);
+ g_clear_object (&self->context);
+ _gtk_source_completion_list_set_context (self->display, NULL);
- completion->providers = g_list_append (completion->providers,
- g_object_ref (provider));
+ self->block_count--;
+}
+
+GtkSourceCompletionList *
+_gtk_source_completion_get_display (GtkSourceCompletion *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (self), NULL);
- if (error != NULL)
+ if (self->display == NULL)
{
- *error = NULL;
+ self->display = _gtk_source_completion_list_new ();
+ _gtk_source_completion_list_set_n_rows (self->display, self->page_size);
+ _gtk_source_completion_list_set_font_desc (self->display, self->font_desc);
+ _gtk_source_assistant_set_mark (GTK_SOURCE_ASSISTANT (self->display),
+ self->completion_mark);
+ _gtk_source_view_add_assistant (self->view,
+ GTK_SOURCE_ASSISTANT (self->display));
+ _gtk_source_completion_list_set_context (self->display, self->context);
}
- return TRUE;
+ return self->display;
}
/**
- * gtk_source_completion_remove_provider:
- * @completion: a #GtkSourceCompletion.
- * @provider: a #GtkSourceCompletionProvider.
- * @error: a #GError.
+ * gtk_source_completion_fuzzy_match:
+ * @haystack: (nullable): the string to be searched.
+ * @casefold_needle: A g_utf8_casefold() version of the needle.
+ * @priority: (out) (allow-none): An optional location for the score of the match
+ *
+ * This helper function can do a fuzzy match for you giving a haystack and
+ * casefolded needle. Casefold your needle using g_utf8_casefold() before
+ * running the query.
*
- * Remove @provider from the completion.
+ * Score will be set with the score of the match upon success. Otherwise,
+ * it will be set to zero.
*
- * Returns: %TRUE if @provider was successfully removed, otherwise if @error
- * is provided, it will be set with the error and %FALSE is returned.
+ * Returns: %TRUE if @haystack matched @casefold_needle, otherwise %FALSE.
+ *
+ * Since: 5.0
*/
gboolean
-gtk_source_completion_remove_provider (GtkSourceCompletion *completion,
- GtkSourceCompletionProvider *provider,
- GError **error)
+gtk_source_completion_fuzzy_match (const char *haystack,
+ const char *casefold_needle,
+ guint *priority)
{
- GList *item;
-
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), FALSE);
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider), FALSE);
-
- item = g_list_find (completion->providers, provider);
-
- if (item == NULL)
- {
- if (error != NULL)
- {
- g_set_error (error,
- GTK_SOURCE_COMPLETION_ERROR,
- GTK_SOURCE_COMPLETION_ERROR_NOT_BOUND,
- "Provider is not bound to this completion object");
- }
+ gint real_score = 0;
+ if (haystack == NULL || haystack[0] == 0)
return FALSE;
- }
- completion->providers = g_list_delete_link (completion->providers, item);
+ for (; *casefold_needle; casefold_needle = g_utf8_next_char (casefold_needle))
+ {
+ gunichar ch = g_utf8_get_char (casefold_needle);
+ gunichar chup = g_unichar_toupper (ch);
+ const gchar *tmp;
+ const gchar *downtmp;
+ const gchar *uptmp;
+
+ /*
+ * Note that the following code is not really correct. We want
+ * to be relatively fast here, but we also don't want to convert
+ * strings to casefolded versions for querying on each compare.
+ * So we use the casefold version and compare with upper. This
+ * works relatively well since we are usually dealing with ASCII
+ * for function names and symbols.
+ */
+
+ downtmp = strchr (haystack, ch);
+ uptmp = strchr (haystack, chup);
+
+ if (downtmp && uptmp)
+ tmp = MIN (downtmp, uptmp);
+ else if (downtmp)
+ tmp = downtmp;
+ else if (uptmp)
+ tmp = uptmp;
+ else
+ return FALSE;
+
+ /*
+ * Here we calculate the cost of this character into the score.
+ * If we matched exactly on the next character, the cost is ZERO.
+ * However, if we had to skip some characters, we have a cost
+ * of 2*distance to the character. This is necessary so that
+ * when we add the cost of the remaining haystack, strings which
+ * exhausted @casefold_needle score lower (higher priority) than
+ * strings which had to skip characters but matched the same
+ * number of characters in the string.
+ */
+ real_score += (tmp - haystack) * 2;
- g_object_unref (provider);
+ /* Add extra cost if we matched by using toupper */
+ if ((gunichar)*haystack == chup)
+ real_score += 1;
- if (error != NULL)
- {
- *error = NULL;
+ /*
+ * * Now move past our matching character so we cannot match
+ * * it a second time.
+ * */
+ haystack = tmp + 1;
}
+ if (priority != NULL)
+ *priority = real_score + strlen (haystack);
+
return TRUE;
}
-/**
- * gtk_source_completion_hide:
- * @completion: a #GtkSourceCompletion.
- *
- * Hides the completion if it is active (visible).
- */
-void
-gtk_source_completion_hide (GtkSourceCompletion *completion)
+static void
+add_attributes (PangoAttrList **attrs,
+ guint begin,
+ guint end)
{
- g_return_if_fail (GTK_SOURCE_IS_COMPLETION (completion));
+ PangoAttribute *attr;
- reset_completion (completion);
-
- if (gtk_widget_get_visible (GTK_WIDGET (completion->main_window)))
+ if (*attrs == NULL)
{
- g_signal_emit (completion, signals[HIDE], 0);
+ *attrs = pango_attr_list_new ();
}
-}
-/**
- * gtk_source_completion_get_view:
- * @completion: a #GtkSourceCompletion.
- *
- * The #GtkSourceView associated with @completion, or %NULL if the view has been
- * destroyed.
- *
- * Returns: (nullable) (transfer none): The #GtkSourceView associated with
- * @completion, or %NULL.
- */
-GtkSourceView *
-gtk_source_completion_get_view (GtkSourceCompletion *completion)
-{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), NULL);
+ attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
+ attr->start_index = begin;
+ attr->end_index = end;
+ pango_attr_list_insert (*attrs, g_steal_pointer (&attr));
- return completion->view;
+ attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
+ attr->start_index = begin;
+ attr->end_index = end;
+ pango_attr_list_insert (*attrs, g_steal_pointer (&attr));
}
/**
- * gtk_source_completion_create_context:
- * @completion: a #GtkSourceCompletion.
- * @position: (nullable): a #GtkTextIter, or %NULL.
+ * gtk_source_completion_fuzzy_highlight:
+ * @haystack: the string to be highlighted
+ * @casefold_query: the typed-text used to highlight @haystack
+ * @attrs: a #PangoAttrList to add the attributes to
+ *
+ * This will add <b> tags around matched characters in @haystack
+ * based on @casefold_query.
*
- * Create a new #GtkSourceCompletionContext for @completion. The position where
- * the completion occurs can be specified by @position. If @position is %NULL,
- * the current cursor position will be used.
+ * Returns: (transfer full) (nullable): a #PangoAttrList or %NULL
*
- * Returns: (transfer floating): a new #GtkSourceCompletionContext.
- * The reference being returned is a 'floating' reference,
- * so if you invoke gtk_source_completion_start() with this context
- * you don't need to unref it.
+ * Since: 5.0
*/
-GtkSourceCompletionContext *
-gtk_source_completion_create_context (GtkSourceCompletion *completion,
- GtkTextIter *position)
+PangoAttrList *
+gtk_source_completion_fuzzy_highlight (const char *haystack,
+ const char *casefold_query)
{
- GtkTextIter iter;
-
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), NULL);
+ const char *real_haystack = haystack;
+ PangoAttrList *attrs = NULL;
+ gunichar str_ch;
+ gunichar match_ch;
+ gboolean element_open = FALSE;
+ guint begin = 0;
+ guint end = 0;
- if (completion->view == NULL)
+ if (haystack == NULL || casefold_query == NULL)
{
return NULL;
}
- if (position == NULL)
- {
- get_iter_at_insert (completion, &iter);
- }
- else
+ for (; *haystack; haystack = g_utf8_next_char (haystack))
{
- iter = *position;
- }
-
- return _gtk_source_completion_context_new (completion, &iter);
-}
+ str_ch = g_utf8_get_char (haystack);
+ match_ch = g_utf8_get_char (casefold_query);
-/**
- * gtk_source_completion_block_interactive:
- * @completion: a #GtkSourceCompletion.
- *
- * Block interactive completion. This can be used to disable interactive
- * completion when inserting or deleting text from the buffer associated with
- * the completion. Use gtk_source_completion_unblock_interactive() to enable
- * interactive completion again.
- *
- * This function may be called multiple times. It will continue to block
- * interactive completion until gtk_source_completion_unblock_interactive()
- * has been called the same number of times.
- */
-void
-gtk_source_completion_block_interactive (GtkSourceCompletion *completion)
-{
- g_return_if_fail (GTK_SOURCE_IS_COMPLETION (completion));
+ if ((str_ch == match_ch) || (g_unichar_tolower (str_ch) == g_unichar_tolower (match_ch)))
+ {
+ if (!element_open)
+ {
+ begin = haystack - real_haystack;
+ element_open = TRUE;
+ }
- if (completion->view == NULL)
- {
- return;
+ /* TODO: We could seek to the next char and append in a batch. */
+ casefold_query = g_utf8_next_char (casefold_query);
+ }
+ else
+ {
+ if (element_open)
+ {
+ end = haystack - real_haystack;
+ add_attributes (&attrs, begin, end);
+ element_open = FALSE;
+ }
+ }
}
- if (completion->block_interactive_num == 0)
+ if (element_open)
{
- block_interactive (completion);
+ end = haystack - real_haystack;
+ add_attributes (&attrs, begin, end);
}
- completion->block_interactive_num++;
+ return g_steal_pointer (&attrs);
}
-/**
- * gtk_source_completion_unblock_interactive:
- * @completion: a #GtkSourceCompletion.
- *
- * Unblock interactive completion. This can be used after using
- * gtk_source_completion_block_interactive() to enable interactive completion
- * again.
- */
void
-gtk_source_completion_unblock_interactive (GtkSourceCompletion *completion)
+_gtk_source_completion_css_changed (GtkSourceCompletion *self,
+ GtkCssStyleChange *change)
{
- g_return_if_fail (GTK_SOURCE_IS_COMPLETION (completion));
-
- if (completion->view == NULL)
- {
- return;
- }
-
- if (completion->block_interactive_num == 1)
- {
- g_signal_handlers_unblock_by_func (completion->buffer,
- buffer_insert_text_cb,
- completion);
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION (self));
+ g_return_if_fail (change != NULL);
- g_signal_handlers_unblock_by_func (completion->buffer,
- buffer_delete_range_cb,
- completion);
- }
+ g_clear_pointer (&self->font_desc, pango_font_description_free);
+ self->font_desc = create_font_description (self);
- if (completion->block_interactive_num > 0)
+ if (self->display != NULL)
{
- completion->block_interactive_num--;
+ _gtk_source_completion_list_set_font_desc (self->display, self->font_desc);
}
}
diff --git a/gtksourceview/gtksourcecompletion.h b/gtksourceview/gtksourcecompletion.h
index f314a816..01fcc742 100644
--- a/gtksourceview/gtksourcecompletion.h
+++ b/gtksourceview/gtksourcecompletion.h
@@ -1,8 +1,7 @@
/*
* This file is part of GtkSourceView
*
- * Copyright 2007 - 2009 Jesús Barbero Rodríguez <chuchiperriman gmail com>
- * Copyright 2009 - Jesse van den Kieboom <jessevdk gnome org>
+ * Copyright 2020 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
@@ -16,79 +15,52 @@
*
* 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
-/*
- * Type checking and casting macros
- */
#define GTK_SOURCE_TYPE_COMPLETION (gtk_source_completion_get_type())
-GTK_SOURCE_AVAILABLE_IN_ALL
+GTK_SOURCE_AVAILABLE_IN_5_0
G_DECLARE_FINAL_TYPE (GtkSourceCompletion, gtk_source_completion, GTK_SOURCE, COMPLETION, GObject)
-/**
- * GTK_SOURCE_COMPLETION_ERROR:
- *
- * Error domain for the completion. Errors in this domain will be from the
- * #GtkSourceCompletionError enumeration. See #GError for more information on
- * error domains.
- */
-#define GTK_SOURCE_COMPLETION_ERROR (gtk_source_completion_error_quark())
-
-/**
- * GtkSourceCompletionError:
- * @GTK_SOURCE_COMPLETION_ERROR_ALREADY_BOUND: The #GtkSourceCompletionProvider
- * is already bound to the #GtkSourceCompletion object.
- * @GTK_SOURCE_COMPLETION_ERROR_NOT_BOUND: The #GtkSourceCompletionProvider is
- * not bound to the #GtkSourceCompletion object.
- *
- * An error code used with %GTK_SOURCE_COMPLETION_ERROR in a #GError returned
- * from a completion-related function.
- */
-typedef enum _GtkSourceCompletionError
-{
- GTK_SOURCE_COMPLETION_ERROR_ALREADY_BOUND = 0,
- GTK_SOURCE_COMPLETION_ERROR_NOT_BOUND
-} GtkSourceCompletionError;
-
-GTK_SOURCE_AVAILABLE_IN_ALL
-GQuark gtk_source_completion_error_quark (void);
-GTK_SOURCE_AVAILABLE_IN_ALL
-gboolean gtk_source_completion_add_provider (GtkSourceCompletion
*completion,
- GtkSourceCompletionProvider
*provider,
- GError **error);
-GTK_SOURCE_AVAILABLE_IN_ALL
-gboolean gtk_source_completion_remove_provider (GtkSourceCompletion
*completion,
- GtkSourceCompletionProvider
*provider,
- GError **error);
-GTK_SOURCE_AVAILABLE_IN_ALL
-GList *gtk_source_completion_get_providers (GtkSourceCompletion
*completion);
-GTK_SOURCE_AVAILABLE_IN_ALL
-gboolean gtk_source_completion_start (GtkSourceCompletion
*completion,
- GList
*providers,
- GtkSourceCompletionContext
*context);
-GTK_SOURCE_AVAILABLE_IN_ALL
-void gtk_source_completion_hide (GtkSourceCompletion
*completion);
-GTK_SOURCE_AVAILABLE_IN_ALL
-GtkSourceView *gtk_source_completion_get_view (GtkSourceCompletion
*completion);
-GTK_SOURCE_AVAILABLE_IN_ALL
-GtkSourceCompletionContext *gtk_source_completion_create_context (GtkSourceCompletion
*completion,
- GtkTextIter
*position);
-GTK_SOURCE_AVAILABLE_IN_ALL
-void gtk_source_completion_block_interactive (GtkSourceCompletion
*completion);
-GTK_SOURCE_AVAILABLE_IN_ALL
-void gtk_source_completion_unblock_interactive (GtkSourceCompletion
*completion);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceView *gtk_source_completion_get_view (GtkSourceCompletion *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceBuffer *gtk_source_completion_get_buffer (GtkSourceCompletion *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_show (GtkSourceCompletion *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_hide (GtkSourceCompletion *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_add_provider (GtkSourceCompletion *self,
+ GtkSourceCompletionProvider *provider);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_remove_provider (GtkSourceCompletion *self,
+ GtkSourceCompletionProvider *provider);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_block_interactive (GtkSourceCompletion *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_unblock_interactive (GtkSourceCompletion *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+guint gtk_source_completion_get_page_size (GtkSourceCompletion *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_set_page_size (GtkSourceCompletion *self,
+ guint page_size);
+GTK_SOURCE_AVAILABLE_IN_5_0
+gboolean gtk_source_completion_fuzzy_match (const char *haystack,
+ const char *casefold_needle,
+ guint *priority);
+GTK_SOURCE_AVAILABLE_IN_5_0
+PangoAttrList *gtk_source_completion_fuzzy_highlight (const char *haystack,
+ const char *casefold_query);
G_END_DECLS
diff --git a/gtksourceview/gtksourcecompletioncell-private.h b/gtksourceview/gtksourcecompletioncell-private.h
new file mode 100644
index 00000000..ea01f232
--- /dev/null
+++ b/gtksourceview/gtksourcecompletioncell-private.h
@@ -0,0 +1,32 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 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 "gtksourcecompletioncell.h"
+
+G_BEGIN_DECLS
+
+void _gtk_source_completion_cell_set_attrs (GtkSourceCompletionCell *self,
+ PangoAttrList *attrs);
+gboolean _gtk_source_completion_cell_is_empty (GtkSourceCompletionCell *self);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcecompletioncell.c b/gtksourceview/gtksourcecompletioncell.c
new file mode 100644
index 00000000..a2a7356e
--- /dev/null
+++ b/gtksourceview/gtksourcecompletioncell.c
@@ -0,0 +1,419 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 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 "gtksource-enumtypes.h"
+#include "gtksourcecompletioncell.h"
+
+struct _GtkSourceCompletionCell
+{
+ GtkWidget widget;
+ GtkSourceCompletionColumn column;
+ GtkWidget *child;
+ PangoAttrList *attrs;
+};
+
+enum {
+ PROP_0,
+ PROP_COLUMN,
+ PROP_MARKUP,
+ PROP_PAINTABLE,
+ PROP_TEXT,
+ PROP_WIDGET,
+ N_PROPS
+};
+
+G_DEFINE_TYPE (GtkSourceCompletionCell, gtk_source_completion_cell, GTK_TYPE_WIDGET)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gtk_source_completion_cell_set_column (GtkSourceCompletionCell *self,
+ GtkSourceCompletionColumn column)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION_CELL (self));
+
+ self->column = column;
+
+ switch (column)
+ {
+ case GTK_SOURCE_COMPLETION_COLUMN_ICON:
+ gtk_widget_add_css_class (GTK_WIDGET (self), "icon");
+ break;
+
+ case GTK_SOURCE_COMPLETION_COLUMN_BEFORE:
+ gtk_widget_add_css_class (GTK_WIDGET (self), "before");
+ break;
+
+ case GTK_SOURCE_COMPLETION_COLUMN_TYPED_TEXT:
+ gtk_widget_add_css_class (GTK_WIDGET (self), "typed-text");
+ break;
+
+ case GTK_SOURCE_COMPLETION_COLUMN_AFTER:
+ gtk_widget_add_css_class (GTK_WIDGET (self), "after");
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+gtk_source_completion_cell_dispose (GObject *object)
+{
+ GtkSourceCompletionCell *self = (GtkSourceCompletionCell *)object;
+
+ g_clear_pointer (&self->child, gtk_widget_unparent);
+ g_clear_pointer (&self->attrs, pango_attr_list_unref);
+
+ G_OBJECT_CLASS (gtk_source_completion_cell_parent_class)->dispose (object);
+}
+
+static void
+gtk_source_completion_cell_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkSourceCompletionCell *self = GTK_SOURCE_COMPLETION_CELL (object);
+
+ switch (prop_id)
+ {
+ case PROP_COLUMN:
+ g_value_set_enum (value, self->column);
+ break;
+
+ case PROP_TEXT:
+ if (GTK_IS_LABEL (self->child))
+ g_value_set_string (value, gtk_label_get_label (GTK_LABEL (self->child)));
+ break;
+
+ case PROP_MARKUP:
+ if (GTK_IS_LABEL (self->child) &&
+ gtk_label_get_use_markup (GTK_LABEL (self->child)))
+ g_value_set_string (value, gtk_label_get_label (GTK_LABEL (self->child)));
+ break;
+
+ case PROP_PAINTABLE:
+ if (GTK_IS_IMAGE (self->child))
+ g_value_set_object (value, gtk_image_get_paintable (GTK_IMAGE (self->child)));
+ break;
+
+ case PROP_WIDGET:
+ g_value_set_object (value, self->child);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtk_source_completion_cell_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkSourceCompletionCell *self = GTK_SOURCE_COMPLETION_CELL (object);
+
+ switch (prop_id)
+ {
+ case PROP_COLUMN:
+ gtk_source_completion_cell_set_column (self, g_value_get_enum (value));
+ break;
+
+ case PROP_MARKUP:
+ gtk_source_completion_cell_set_markup (self, g_value_get_string (value));
+ break;
+
+ case PROP_TEXT:
+ gtk_source_completion_cell_set_text (self, g_value_get_string (value));
+ break;
+
+ case PROP_PAINTABLE:
+ gtk_source_completion_cell_set_paintable (self, g_value_get_object (value));
+ break;
+
+ case PROP_WIDGET:
+ gtk_source_completion_cell_set_widget (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtk_source_completion_cell_class_init (GtkSourceCompletionCellClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = gtk_source_completion_cell_dispose;
+ object_class->get_property = gtk_source_completion_cell_get_property;
+ object_class->set_property = gtk_source_completion_cell_set_property;
+
+ properties [PROP_COLUMN] =
+ g_param_spec_enum ("column",
+ "Column",
+ "Column",
+ GTK_SOURCE_TYPE_COMPLETION_COLUMN,
+ GTK_SOURCE_COMPLETION_COLUMN_TYPED_TEXT,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_MARKUP] =
+ g_param_spec_string ("markup",
+ "Markup",
+ "Markup",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TEXT] =
+ g_param_spec_string ("text",
+ "Text",
+ "Text",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_PAINTABLE] =
+ g_param_spec_object ("paintable",
+ "Paintable",
+ "Paintable",
+ GDK_TYPE_PAINTABLE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_WIDGET] =
+ g_param_spec_object ("widget",
+ "Widget",
+ "Widget",
+ GTK_TYPE_WIDGET,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_css_name (widget_class, "cell");
+ gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+}
+
+static void
+gtk_source_completion_cell_init (GtkSourceCompletionCell *self)
+{
+ gtk_widget_add_css_class (GTK_WIDGET (self), "cell");
+}
+
+void
+gtk_source_completion_cell_set_markup (GtkSourceCompletionCell *self,
+ const char *markup)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CELL (self));
+
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CELL (self));
+
+ if (!GTK_IS_LABEL (self->child))
+ {
+ GtkWidget *child = gtk_label_new (NULL);
+ gtk_source_completion_cell_set_widget (self, child);
+ }
+
+ gtk_label_set_text (GTK_LABEL (self->child), markup);
+ gtk_label_set_use_markup (GTK_LABEL (self->child), TRUE);
+}
+
+void
+gtk_source_completion_cell_set_text (GtkSourceCompletionCell *self,
+ const char *text)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CELL (self));
+
+ if (!GTK_IS_LABEL (self->child))
+ {
+ GtkWidget *child = gtk_label_new (NULL);
+ gtk_source_completion_cell_set_widget (self, child);
+ }
+
+ gtk_label_set_use_markup (GTK_LABEL (self->child), FALSE);
+ gtk_label_set_text (GTK_LABEL (self->child), text);
+}
+
+void
+gtk_source_completion_cell_set_text_with_attributes (GtkSourceCompletionCell *self,
+ const char *text,
+ PangoAttrList *attrs)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CELL (self));
+
+ gtk_source_completion_cell_set_text (self, text);
+
+ if (attrs != NULL)
+ {
+ if (self->attrs != NULL)
+ {
+ PangoAttrList *copy = pango_attr_list_copy (self->attrs);
+ pango_attr_list_splice (copy, attrs, 0, 0);
+ gtk_label_set_attributes (GTK_LABEL (self->child), copy);
+ g_clear_pointer (©, pango_attr_list_unref);
+ }
+ else
+ {
+ gtk_label_set_attributes (GTK_LABEL (self->child), attrs);
+ }
+ }
+ else
+ {
+ gtk_label_set_attributes (GTK_LABEL (self->child), self->attrs);
+ }
+}
+
+void
+gtk_source_completion_cell_set_paintable (GtkSourceCompletionCell *self,
+ GdkPaintable *paintable)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CELL (self));
+ g_return_if_fail (!paintable || GDK_IS_PAINTABLE (paintable));
+
+ gtk_source_completion_cell_set_widget (self, gtk_image_new_from_paintable (paintable));
+}
+
+/**
+ * gtk_source_completion_cell_get_widget:
+ * @self: a #GtkSourceCompletionCell
+ *
+ * Gets the child #GtkWidget, if any.
+ *
+ * Returns: (transfer none) (nullable): a #GtkWidget or %NULL
+ *
+ * Since: 5.0
+ */
+GtkWidget *
+gtk_source_completion_cell_get_widget (GtkSourceCompletionCell *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CELL (self), NULL);
+
+ return self->child;
+}
+
+void
+gtk_source_completion_cell_set_widget (GtkSourceCompletionCell *self,
+ GtkWidget *widget)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CELL (self));
+ g_return_if_fail (!widget || GTK_IS_WIDGET (widget));
+ g_return_if_fail (!widget || gtk_widget_get_parent (widget) == NULL);
+
+ if (widget == self->child)
+ return;
+
+ g_clear_pointer (&self->child, gtk_widget_unparent);
+
+ if (widget != NULL)
+ {
+ self->child = widget;
+ gtk_widget_set_parent (widget, GTK_WIDGET (self));
+
+ if (GTK_IS_LABEL (widget))
+ {
+ gtk_label_set_attributes (GTK_LABEL (widget), self->attrs);
+
+ if (self->column == GTK_SOURCE_COMPLETION_COLUMN_BEFORE)
+ {
+ gtk_label_set_xalign (GTK_LABEL (widget), 1.0);
+ }
+ else if (self->column == GTK_SOURCE_COMPLETION_COLUMN_AFTER ||
+ self->column == GTK_SOURCE_COMPLETION_COLUMN_TYPED_TEXT ||
+ self->column == GTK_SOURCE_COMPLETION_COLUMN_COMMENT ||
+ self->column == GTK_SOURCE_COMPLETION_COLUMN_DETAILS)
+ {
+ gtk_label_set_xalign (GTK_LABEL (widget), 0.0);
+ }
+
+ if (self->column == GTK_SOURCE_COMPLETION_COLUMN_COMMENT)
+ {
+ gtk_label_set_xalign (GTK_LABEL (widget), 0.0);
+ gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END);
+ gtk_label_set_lines (GTK_LABEL (widget), 3);
+ gtk_label_set_wrap (GTK_LABEL (widget), TRUE);
+ gtk_label_set_max_width_chars (GTK_LABEL (widget), 50);
+ gtk_widget_set_valign (widget, GTK_ALIGN_BASELINE);
+ }
+ }
+ else if (GTK_IS_IMAGE (widget))
+ {
+ if (self->column == GTK_SOURCE_COMPLETION_COLUMN_AFTER)
+ {
+ gtk_widget_set_halign (widget, GTK_ALIGN_END);
+ }
+ }
+ }
+}
+
+GtkSourceCompletionColumn
+gtk_source_completion_cell_get_column (GtkSourceCompletionCell *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CELL (self), 0);
+
+ return self->column;
+}
+
+void
+_gtk_source_completion_cell_set_attrs (GtkSourceCompletionCell *self,
+ PangoAttrList *attrs)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CELL (self));
+
+ if (attrs != self->attrs)
+ {
+ g_clear_pointer (&self->attrs, pango_attr_list_unref);
+
+ if (attrs)
+ {
+ self->attrs = pango_attr_list_ref (attrs);
+ }
+
+ if (GTK_IS_LABEL (self->child))
+ {
+ gtk_label_set_attributes (GTK_LABEL (self->child), attrs);
+ }
+ }
+}
+
+void
+gtk_source_completion_cell_set_icon_name (GtkSourceCompletionCell *self,
+ const char *icon_name)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CELL (self));
+
+ if (!GTK_IS_IMAGE (self->child))
+ {
+ GtkWidget *image = gtk_image_new ();
+ gtk_source_completion_cell_set_widget (self, image);
+ }
+
+ gtk_image_set_from_icon_name (GTK_IMAGE (self->child), icon_name);
+}
+
+gboolean
+_gtk_source_completion_cell_is_empty (GtkSourceCompletionCell *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CELL (self), FALSE);
+
+ return self->child == NULL;
+}
diff --git a/gtksourceview/gtksourcecompletioncell.h b/gtksourceview/gtksourcecompletioncell.h
new file mode 100644
index 00000000..6e5ff291
--- /dev/null
+++ b/gtksourceview/gtksourcecompletioncell.h
@@ -0,0 +1,69 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 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 <gtk/gtk.h>
+
+#include "gtksourcetypes.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_COMPLETION_CELL (gtk_source_completion_cell_get_type())
+
+typedef enum _GtkSourceCompletionColumn
+{
+ GTK_SOURCE_COMPLETION_COLUMN_ICON = 0,
+ GTK_SOURCE_COMPLETION_COLUMN_BEFORE = 1,
+ GTK_SOURCE_COMPLETION_COLUMN_TYPED_TEXT = 2,
+ GTK_SOURCE_COMPLETION_COLUMN_AFTER = 3,
+ GTK_SOURCE_COMPLETION_COLUMN_COMMENT = 4,
+ GTK_SOURCE_COMPLETION_COLUMN_DETAILS = 5,
+} GtkSourceCompletionColumn;
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+G_DECLARE_FINAL_TYPE (GtkSourceCompletionCell, gtk_source_completion_cell, GTK_SOURCE, COMPLETION_CELL,
GtkWidget)
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceCompletionColumn gtk_source_completion_cell_get_column (GtkSourceCompletionCell
*self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkWidget *gtk_source_completion_cell_get_widget (GtkSourceCompletionCell
*self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_cell_set_widget (GtkSourceCompletionCell *self,
+ GtkWidget
*child);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_cell_set_markup (GtkSourceCompletionCell *self,
+ const char
*markup);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_cell_set_icon_name (GtkSourceCompletionCell *self,
+ const char
*icon_name);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_cell_set_paintable (GtkSourceCompletionCell *self,
+ GdkPaintable
*paintable);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_cell_set_text (GtkSourceCompletionCell *self,
+ const char
*text);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_cell_set_text_with_attributes (GtkSourceCompletionCell *self,
+ const char *text,
+ PangoAttrList
*attrs);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcecompletioncontext-private.h
b/gtksourceview/gtksourcecompletioncontext-private.h
index cacd3e66..c025315c 100644
--- a/gtksourceview/gtksourcecompletioncontext-private.h
+++ b/gtksourceview/gtksourcecompletioncontext-private.h
@@ -1,7 +1,7 @@
/*
* This file is part of GtkSourceView
*
- * Copyright 2009 - Jesse van den Kieboom <jessevdk gnome org>
+ * Copyright 2020 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
@@ -15,18 +15,42 @@
*
* 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 <gtk/gtk.h>
+
#include "gtksourcecompletioncontext.h"
G_BEGIN_DECLS
-G_GNUC_INTERNAL
-GtkSourceCompletionContext *_gtk_source_completion_context_new (GtkSourceCompletion *completion,
- GtkTextIter *position);
-G_GNUC_INTERNAL
-void _gtk_source_completion_context_cancel (GtkSourceCompletionContext *context);
+GtkSourceCompletionContext *_gtk_source_completion_context_new (GtkSourceCompletion
*completion);
+gboolean _gtk_source_completion_context_get_item_full (GtkSourceCompletionContext
*self,
+ guint
position,
+ GtkSourceCompletionProvider
**provider,
+ GtkSourceCompletionProposal
**proposal);
+void _gtk_source_completion_context_add_provider (GtkSourceCompletionContext
*self,
+ GtkSourceCompletionProvider
*provider);
+void _gtk_source_completion_context_remove_provider (GtkSourceCompletionContext
*self,
+ GtkSourceCompletionProvider
*provider);
+gboolean _gtk_source_completion_context_can_refilter (GtkSourceCompletionContext
*self,
+ const GtkTextIter
*begin,
+ const GtkTextIter
*end);
+void _gtk_source_completion_context_refilter (GtkSourceCompletionContext
*self);
+gboolean _gtk_source_completion_context_iter_invalidates (GtkSourceCompletionContext
*self,
+ const GtkTextIter
*iter);
+void _gtk_source_completion_context_complete_async (GtkSourceCompletionContext
*self,
+ GtkSourceCompletionActivation
activation,
+ const GtkTextIter
*begin,
+ const GtkTextIter
*end,
+ GCancellable
*cancellable,
+ GAsyncReadyCallback
callback,
+ gpointer
user_data);
+gboolean _gtk_source_completion_context_complete_finish (GtkSourceCompletionContext
*self,
+ GAsyncResult
*result,
+ GError
**error);
G_END_DECLS
diff --git a/gtksourceview/gtksourcecompletioncontext.c b/gtksourceview/gtksourcecompletioncontext.c
index 1d715345..78c21182 100644
--- a/gtksourceview/gtksourcecompletioncontext.c
+++ b/gtksourceview/gtksourcecompletioncontext.c
@@ -1,7 +1,7 @@
/*
* This file is part of GtkSourceView
*
- * Copyright 2009 - Jesse van den Kieboom <jessevdk gnome org>
+ * Copyright 2020 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
@@ -15,163 +15,208 @@
*
* 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
*/
-/**
- * SECTION:completioncontext
- * @title: GtkSourceCompletionContext
- * @short_description: The context of a completion
- *
- * Initially, the completion window is hidden. For a completion to occur, it has
- * to be activated. The different possible activations are listed in
- * #GtkSourceCompletionActivation. When an activation occurs, a
- * #GtkSourceCompletionContext object is created, and the eligible providers are
- * asked to add proposals with gtk_source_completion_context_add_proposals().
- *
- * If no proposals are added, the completion window remains hidden, and the
- * context is destroyed.
- *
- * On the other hand, if proposals are added, the completion window becomes
- * visible, and the user can choose a proposal. If the user is not happy with
- * the shown proposals, he or she can insert or delete characters, to modify the
- * completion context and therefore hoping to see the proposal he or she wants.
- * This means that when an insertion or deletion occurs in the #GtkTextBuffer
- * when the completion window is visible, the eligible providers are again asked
- * to add proposals. The #GtkSourceCompletionContext:activation remains the
- * same in this case.
- *
- * When the completion window is hidden, the interactive completion is triggered
- * only on insertion in the buffer, not on deletion. Once the completion window
- * is visible, then on each insertion or deletion, there is a new population and
- * the providers are asked to add proposals. If there are no more proposals, the
- * completion window disappears. So if you want to keep the completion window
- * visible, but there are no proposals, you can insert a dummy proposal named
- * "No proposals". For example, the user types progressively the name of
- * a function, and some proposals appear. The user types a bad character and
- * there are no proposals anymore. What the user wants is to delete the last
- * character, and see the previous proposals. If the completion window
- * disappears, the previous proposals will not reappear on the character
- * deletion.
- *
- * A #GtkTextIter is associated with the context, this is where the completion
- * takes place. With this #GtkTextIter, you can get the associated
- * #GtkTextBuffer with gtk_text_iter_get_buffer().
- */
+
#include "config.h"
-#include "gtksourcecompletion-private.h"
+#include <string.h>
+
+#include "gtksourcecompletion.h"
#include "gtksourcecompletioncontext-private.h"
-#include "gtksource-enumtypes.h"
+#include "gtksourcecompletionproposal.h"
#include "gtksourcecompletionprovider.h"
-#include "gtksourcecompletion.h"
struct _GtkSourceCompletionContext
{
- GInitiallyUnowned parent_instance;
+ GObject parent_instance;
GtkSourceCompletion *completion;
- GtkTextMark *mark;
+ GArray *providers;
+
+ GtkTextMark *begin_mark;
+ GtkTextMark *end_mark;
+
GtkSourceCompletionActivation activation;
+
+ guint busy : 1;
+ guint has_populated : 1;
+ guint empty : 1;
};
-enum
+typedef struct
+{
+ GtkSourceCompletionProvider *provider;
+ GListModel *results;
+ GError *error;
+ gulong items_changed_handler;
+} ProviderInfo;
+
+typedef struct
{
+ guint n_active;
+} CompleteTaskData;
+
+static void list_model_iface_init (GListModelInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtkSourceCompletionContext, gtk_source_completion_context, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+enum {
PROP_0,
+ PROP_BUSY,
PROP_COMPLETION,
- PROP_ITER,
- PROP_ACTIVATION,
+ PROP_EMPTY,
N_PROPS
};
-enum
+static GParamSpec *properties [N_PROPS];
+
+static void
+clear_provider_info (gpointer data)
{
- CANCELLED,
- N_SIGNALS
-};
+ ProviderInfo *info = data;
-static guint context_signals[N_SIGNALS];
-static GParamSpec *properties[N_PROPS];
+ if (info->items_changed_handler != 0)
+ {
+ g_signal_handler_disconnect (info->results, info->items_changed_handler);
+ info->items_changed_handler = 0;
+ }
-G_DEFINE_TYPE (GtkSourceCompletionContext, gtk_source_completion_context, G_TYPE_INITIALLY_UNOWNED)
+ g_clear_object (&info->provider);
+ g_clear_object (&info->results);
+ g_clear_error (&info->error);
+}
+
+static gint
+compare_provider_info (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ GtkSourceCompletionContext *self = user_data;
+ const ProviderInfo *info_a = a;
+ const ProviderInfo *info_b = b;
+ int priority_a = gtk_source_completion_provider_get_priority (info_a->provider, self);
+ int priority_b = gtk_source_completion_provider_get_priority (info_b->provider, self);
+
+ if (priority_a < priority_b)
+ return -1;
+ else if (priority_a > priority_b)
+ return 1;
+ else
+ return 0;
+}
static void
-gtk_source_completion_context_dispose (GObject *object)
+complete_task_data_free (gpointer data)
{
- GtkSourceCompletionContext *context = GTK_SOURCE_COMPLETION_CONTEXT (object);
+ CompleteTaskData *task_data = data;
- if (context->mark != NULL)
+ g_slice_free (CompleteTaskData, task_data);
+}
+
+static void
+gtk_source_completion_context_update_empty (GtkSourceCompletionContext *self)
+{
+ gboolean empty = TRUE;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_CONTEXT (self));
+
+ for (guint i = 0; i < self->providers->len; i++)
{
- GtkTextBuffer *buffer = gtk_text_mark_get_buffer (context->mark);
+ const ProviderInfo *info = &g_array_index (self->providers, ProviderInfo, i);
- if (buffer != NULL)
+ if (info->results != NULL && g_list_model_get_n_items (info->results) > 0)
{
- gtk_text_buffer_delete_mark (buffer, context->mark);
+ empty = FALSE;
+ break;
}
-
- g_clear_object (&context->mark);
}
- g_clear_object (&context->completion);
-
- G_OBJECT_CLASS (gtk_source_completion_context_parent_class)->dispose (object);
+ if (self->empty != empty)
+ {
+ self->empty = empty;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EMPTY]);
+ }
}
static void
-set_iter (GtkSourceCompletionContext *context,
- GtkTextIter *iter)
+gtk_source_completion_context_mark_failed (GtkSourceCompletionContext *self,
+ GtkSourceCompletionProvider *provider,
+ const GError *error)
{
- GtkTextBuffer *buffer;
+ g_assert (GTK_SOURCE_IS_COMPLETION_CONTEXT (self));
+ g_assert (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
+ g_assert (error != NULL);
- buffer = gtk_text_iter_get_buffer (iter);
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) ||
+ g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
+ return;
- if (context->mark != NULL)
+ for (guint i = 0; i < self->providers->len; i++)
{
- GtkTextBuffer *old_buffer;
-
- old_buffer = gtk_text_mark_get_buffer (context->mark);
+ ProviderInfo *info = &g_array_index (self->providers, ProviderInfo, i);
- if (old_buffer != buffer)
+ if (info->provider == provider)
{
- g_object_unref (context->mark);
- context->mark = NULL;
+ if (error != info->error)
+ {
+ g_clear_error (&info->error);
+ info->error = g_error_copy (error);
+ }
+
+ break;
}
}
+}
- if (context->mark == NULL)
+static void
+gtk_source_completion_context_dispose (GObject *object)
+{
+ GtkSourceCompletionContext *self = (GtkSourceCompletionContext *)object;
+
+ g_clear_pointer (&self->providers, g_array_unref);
+ g_clear_object (&self->completion);
+
+ if (self->begin_mark != NULL)
{
- context->mark = gtk_text_buffer_create_mark (buffer, NULL, iter, FALSE);
- g_object_ref (context->mark);
+ gtk_text_buffer_delete_mark (gtk_text_mark_get_buffer (self->begin_mark), self->begin_mark);
+ g_clear_object (&self->begin_mark);
}
- else
+
+ if (self->end_mark != NULL)
{
- gtk_text_buffer_move_mark (buffer, context->mark, iter);
+ gtk_text_buffer_delete_mark (gtk_text_mark_get_buffer (self->end_mark), self->end_mark);
+ g_clear_object (&self->end_mark);
}
- g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_ITER]);
+ G_OBJECT_CLASS (gtk_source_completion_context_parent_class)->dispose (object);
}
static void
-gtk_source_completion_context_set_property (GObject *object,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
+gtk_source_completion_context_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
{
- GtkSourceCompletionContext *context = GTK_SOURCE_COMPLETION_CONTEXT (object);
+ GtkSourceCompletionContext *self = GTK_SOURCE_COMPLETION_CONTEXT (object);
switch (prop_id)
{
- case PROP_COMPLETION:
- context->completion = g_value_dup_object (value);
+ case PROP_BUSY:
+ g_value_set_boolean (value, gtk_source_completion_context_get_busy (self));
break;
- case PROP_ITER:
- set_iter (context, g_value_get_boxed (value));
+ case PROP_COMPLETION:
+ g_value_set_object (value, gtk_source_completion_context_get_completion (self));
break;
- case PROP_ACTIVATION:
- context->activation = g_value_get_flags (value);
+ case PROP_EMPTY:
+ g_value_set_boolean (value, gtk_source_completion_context_get_empty (self));
break;
default:
@@ -180,32 +225,17 @@ gtk_source_completion_context_set_property (GObject *object,
}
static void
-gtk_source_completion_context_get_property (GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
+gtk_source_completion_context_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
{
- GtkSourceCompletionContext *context = GTK_SOURCE_COMPLETION_CONTEXT (object);
+ GtkSourceCompletionContext *self = GTK_SOURCE_COMPLETION_CONTEXT (object);
switch (prop_id)
{
case PROP_COMPLETION:
- g_value_set_object (value, context->completion);
- break;
-
- case PROP_ITER:
- {
- GtkTextIter iter;
-
- if (gtk_source_completion_context_get_iter (context, &iter))
- {
- g_value_set_boxed (value, &iter);
- }
- }
- break;
-
- case PROP_ACTIVATION:
- g_value_set_flags (value, context->activation);
+ self->completion = g_value_dup_object (value);
break;
default:
@@ -218,191 +248,760 @@ gtk_source_completion_context_class_init (GtkSourceCompletionContextClass *klass
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
- object_class->set_property = gtk_source_completion_context_set_property;
- object_class->get_property = gtk_source_completion_context_get_property;
object_class->dispose = gtk_source_completion_context_dispose;
+ object_class->get_property = gtk_source_completion_context_get_property;
+ object_class->set_property = gtk_source_completion_context_set_property;
/**
- * GtkSourceCompletionContext::cancelled:
- *
- * Emitted when the current population of proposals has been cancelled.
- * Providers adding proposals asynchronously should connect to this signal
- * to know when to cancel running proposal queries.
- **/
- context_signals[CANCELLED] =
- g_signal_new_class_handler ("cancelled",
- G_TYPE_FROM_CLASS (klass),
- G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
- NULL, NULL, NULL, NULL, G_TYPE_NONE, 0);
+ * GtkSourceCompletionContext:busy:
+ *
+ * The "busy" property is %TRUE while the completion context is
+ * populating completion proposals.
+ *
+ * Since: 5.0
+ */
+ properties [PROP_BUSY] =
+ g_param_spec_boolean ("busy",
+ "Busy",
+ "Is the completion context busy populating",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
/**
- * GtkSourceCompletionContext:completion:
- *
- * The #GtkSourceCompletion associated with the context.
- **/
+ * GtkSourceCompletionContext:empty:
+ *
+ * The "empty" property is %TRUE when there are no results.
+ *
+ * It will be notified when the first result is added or the last
+ * result is removed.
+ *
+ * Since: 5.0
+ */
+ properties [PROP_EMPTY] =
+ g_param_spec_boolean ("empty",
+ "Empty",
+ "If the context has no results",
+ TRUE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GtkSourceCompletionContext:completion:
+ *
+ * The "completion" is the #GtkSourceCompletion that was used to create the context.
+ *
+ * Since: 5.0
+ */
properties [PROP_COMPLETION] =
g_param_spec_object ("completion",
"Completion",
- "The completion object to which the context belongs",
+ "Completion",
GTK_SOURCE_TYPE_COMPLETION,
- (G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT_ONLY |
- G_PARAM_STATIC_STRINGS));
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
- /**
- * GtkSourceCompletionContext:iter:
- *
- * The #GtkTextIter at which the completion is invoked.
- **/
- properties [PROP_ITER] =
- g_param_spec_boxed ("iter",
- "Iterator",
- "The GtkTextIter at which the completion was invoked",
- GTK_TYPE_TEXT_ITER,
- (G_PARAM_READWRITE |
- G_PARAM_EXPLICIT_NOTIFY |
- G_PARAM_STATIC_STRINGS));
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
- /**
- * GtkSourceCompletionContext:activation:
- *
- * The completion activation
- **/
- properties [PROP_ACTIVATION] =
- g_param_spec_flags ("activation",
- "Activation",
- "The type of activation",
- GTK_SOURCE_TYPE_COMPLETION_ACTIVATION,
- GTK_SOURCE_COMPLETION_ACTIVATION_USER_REQUESTED,
- (G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT |
- G_PARAM_STATIC_STRINGS));
+static void
+gtk_source_completion_context_init (GtkSourceCompletionContext *self)
+{
+ self->empty = TRUE;
- g_object_class_install_properties (object_class, N_PROPS, properties);
+ self->providers = g_array_new (FALSE, FALSE, sizeof (ProviderInfo));
+ g_array_set_clear_func (self->providers, clear_provider_info);
+}
+
+static GType
+gtk_source_completion_context_get_item_type (GListModel *model)
+{
+ return GTK_SOURCE_TYPE_COMPLETION_PROPOSAL;
+}
+
+static guint
+gtk_source_completion_context_get_n_items (GListModel *model)
+{
+ GtkSourceCompletionContext *self = (GtkSourceCompletionContext *)model;
+ guint count = 0;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_CONTEXT (self));
+
+ for (guint i = 0; i < self->providers->len; i++)
+ {
+ const ProviderInfo *info = &g_array_index (self->providers, ProviderInfo, i);
+
+ if (info->results != NULL)
+ {
+ count += g_list_model_get_n_items (info->results);
+ }
+ }
+
+ return count;
+}
+
+gboolean
+_gtk_source_completion_context_get_item_full (GtkSourceCompletionContext *self,
+ guint position,
+ GtkSourceCompletionProvider **provider,
+ GtkSourceCompletionProposal **proposal)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (self), FALSE);
+ g_return_val_if_fail (position < G_MAXUINT, FALSE);
+
+ if (provider != NULL)
+ *provider = NULL;
+
+ if (proposal != NULL)
+ *proposal = NULL;
+
+ for (guint i = 0; i < self->providers->len; i++)
+ {
+ const ProviderInfo *info = &g_array_index (self->providers, ProviderInfo, i);
+ guint n_items;
+
+ if (info->results == NULL)
+ continue;
+
+ n_items = g_list_model_get_n_items (info->results);
+
+ if (position >= n_items)
+ {
+ position -= n_items;
+ continue;
+ }
+
+ if (provider != NULL)
+ {
+ *provider = g_object_ref (info->provider);
+ }
+
+ if (proposal != NULL)
+ {
+ *proposal = g_list_model_get_item (info->results, position);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gpointer
+gtk_source_completion_context_get_item (GListModel *model,
+ guint position)
+{
+ GtkSourceCompletionContext *self = (GtkSourceCompletionContext *)model;
+ g_autoptr(GtkSourceCompletionProposal) proposal = NULL;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_CONTEXT (self));
+
+ if (_gtk_source_completion_context_get_item_full (self, position, NULL, &proposal))
+ {
+ return g_steal_pointer (&proposal);
+ }
+
+ return NULL;
}
static void
-gtk_source_completion_context_init (GtkSourceCompletionContext *context)
+list_model_iface_init (GListModelInterface *iface)
+{
+ iface->get_item_type = gtk_source_completion_context_get_item_type;
+ iface->get_item = gtk_source_completion_context_get_item;
+ iface->get_n_items = gtk_source_completion_context_get_n_items;
+}
+
+/**
+ * gtk_source_completion_context_get_bounds:
+ * @self: an #GtkSourceCompletionContext
+ * @begin: (out) (optional): a #GtkTextIter
+ * @end: (out) (optional): a #GtkTextIter
+ *
+ * Gets the bounds for the completion, which is the beginning of the
+ * current word (taking break characters into account) to the current
+ * insertion cursor.
+ *
+ * If @begin is non-%NULL, it will be set to the start position of the
+ * current word being completed.
+ *
+ * If @end is non-%NULL, it will be set to the insertion cursor for the
+ * current word being completed.
+ *
+ * Returns: %TRUE if the marks are still valid and @begin or @end was set.
+ *
+ * Since: 5.0
+ */
+gboolean
+gtk_source_completion_context_get_bounds (GtkSourceCompletionContext *self,
+ GtkTextIter *begin,
+ GtkTextIter *end)
{
- context = gtk_source_completion_context_get_instance_private (context);
+ GtkSourceBuffer *buffer;
+
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (self), FALSE);
+ g_return_val_if_fail (self->completion != NULL, FALSE);
+ g_return_val_if_fail (begin != NULL || end != NULL, FALSE);
+
+ buffer = gtk_source_completion_get_buffer (self->completion);
+
+ g_return_val_if_fail (buffer != NULL, FALSE);
+
+ if (begin != NULL)
+ memset (begin, 0, sizeof *begin);
+
+ if (end != NULL)
+ memset (end, 0, sizeof *end);
+
+ if (self->begin_mark == NULL)
+ {
+ /* Try to give some sort of valid iter */
+ gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer), begin, end);
+ return FALSE;
+ }
+
+ g_assert (GTK_IS_TEXT_MARK (self->begin_mark));
+ g_assert (GTK_IS_TEXT_MARK (self->end_mark));
+ g_assert (GTK_IS_TEXT_BUFFER (buffer));
+
+ if (begin != NULL)
+ {
+ gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), begin, self->begin_mark);
+ }
+
+ if (end != NULL)
+ {
+ gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), end, self->end_mark);
+ }
+
+ return TRUE;
}
/**
- * gtk_source_completion_context_add_proposals:
- * @context: a #GtkSourceCompletionContext.
- * @provider: a #GtkSourceCompletionProvider.
- * @proposals: (nullable) (element-type GtkSource.CompletionProposal): The list of proposals to add.
- * @finished: Whether the provider is finished adding proposals.
- *
- * Providers can use this function to add proposals to the completion. They
- * can do so asynchronously by means of the @finished argument. Providers must
- * ensure that they always call this function with @finished set to %TRUE
- * once each population (even if no proposals need to be added).
- * Population occurs when the gtk_source_completion_provider_populate()
- * function is called.
- **/
+ * gtk_source_completion_context_get_completion:
+ * @self: an #GtkSourceCompletionContext
+ *
+ * Gets the #GtkSourceCompletion that created the context.
+ *
+ * Returns: (transfer none) (nullable): an #GtkSourceCompletion or %NULL
+ *
+ * Since: 5.0
+ */
+GtkSourceCompletion *
+gtk_source_completion_context_get_completion (GtkSourceCompletionContext *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (self), NULL);
+
+ return self->completion;
+}
+
+GtkSourceCompletionContext *
+_gtk_source_completion_context_new (GtkSourceCompletion *completion)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), NULL);
+
+ return g_object_new (GTK_SOURCE_TYPE_COMPLETION_CONTEXT,
+ "completion", completion,
+ NULL);
+}
+
void
-gtk_source_completion_context_add_proposals (GtkSourceCompletionContext *context,
- GtkSourceCompletionProvider *provider,
- GList *proposals,
- gboolean finished)
+_gtk_source_completion_context_add_provider (GtkSourceCompletionContext *self,
+ GtkSourceCompletionProvider *provider)
{
- g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
+ ProviderInfo info = {0};
+
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (self));
g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
+ g_return_if_fail (self->has_populated == FALSE);
- _gtk_source_completion_add_proposals (context->completion,
- context,
- provider,
- proposals,
- finished);
+ info.provider = g_object_ref (provider);
+ info.results = NULL;
+
+ g_array_append_val (self->providers, info);
+ g_array_sort_with_data (self->providers, compare_provider_info, self);
+}
+
+void
+_gtk_source_completion_context_remove_provider (GtkSourceCompletionContext *self,
+ GtkSourceCompletionProvider *provider)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (self));
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
+ g_return_if_fail (self->has_populated == FALSE);
+
+ for (guint i = 0; i < self->providers->len; i++)
+ {
+ const ProviderInfo *info = &g_array_index (self->providers, ProviderInfo, i);
+
+ if (info->provider == provider)
+ {
+ g_array_remove_index (self->providers, i);
+ return;
+ }
+ }
+
+ g_warning ("No such provider <%s %p> in context",
+ G_OBJECT_TYPE_NAME (provider), provider);
+}
+
+static void
+gtk_source_completion_context_items_changed_cb (GtkSourceCompletionContext *self,
+ guint position,
+ guint removed,
+ guint added,
+ GListModel *results)
+{
+ guint real_position = 0;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_CONTEXT (self));
+ g_assert (G_IS_LIST_MODEL (results));
+
+ if (removed == 0 && added == 0)
+ return;
+
+ for (guint i = 0; i < self->providers->len; i++)
+ {
+ ProviderInfo *info = &g_array_index (self->providers, ProviderInfo, i);
+
+ if (info->results == results)
+ {
+ g_list_model_items_changed (G_LIST_MODEL (self),
+ real_position + position,
+ removed,
+ added);
+ break;
+ }
+
+ if (info->results != NULL)
+ real_position += g_list_model_get_n_items (info->results);
+ }
+
+ gtk_source_completion_context_update_empty (self);
}
/**
- * gtk_source_completion_context_get_iter:
- * @context: a #GtkSourceCompletionContext.
- * @iter: (out): a #GtkTextIter.
+ * gtk_source_completion_context_set_proposals_for_provider:
+ * @self: an #GtkSourceCompletionContext
+ * @provider: an #GtkSourceCompletionProvider
+ * @results: (nullable): a #GListModel or %NULL
*
- * Get the iter at which the completion was invoked. Providers can use this
- * to determine how and if to match proposals.
+ * This function allows providers to update their results for a context
+ * outside of a call to gtk_source_completion_provider_populate_async(). This
+ * can be used to immediately return results for a provider while it does
+ * additional asynchronous work. Doing so will allow the completions to
+ * update while the operation is in progress.
*
- * Returns: %TRUE if @iter is correctly set, %FALSE otherwise.
- **/
-gboolean
-gtk_source_completion_context_get_iter (GtkSourceCompletionContext *context,
- GtkTextIter *iter)
+ * Since: 5.0
+ */
+void
+gtk_source_completion_context_set_proposals_for_provider (GtkSourceCompletionContext *self,
+ GtkSourceCompletionProvider *provider,
+ GListModel *results)
{
- GtkTextBuffer *mark_buffer;
- GtkSourceView *view;
- GtkTextBuffer *completion_buffer;
+ guint position = 0;
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), FALSE);
+ g_assert (GTK_SOURCE_IS_COMPLETION_CONTEXT (self));
+ g_assert (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
+ g_assert (!results || G_IS_LIST_MODEL (results));
- if (context->mark == NULL)
+ for (guint i = 0; i < self->providers->len; i++)
{
- /* This should never happen: context should be always be created
- providing a position iter */
- g_warning ("Completion context without mark");
- return FALSE;
+ ProviderInfo *info = &g_array_index (self->providers, ProviderInfo, i);
+
+ if (info->provider == provider)
+ {
+ guint n_removed = 0;
+ guint n_added = 0;
+
+ if (info->results == results)
+ return;
+
+ if (info->results != NULL)
+ n_removed = g_list_model_get_n_items (info->results);
+
+ if (results != NULL)
+ n_added = g_list_model_get_n_items (results);
+
+ if (info->items_changed_handler != 0)
+ {
+ g_signal_handler_disconnect (info->results, info->items_changed_handler);
+ info->items_changed_handler = 0;
+ }
+
+ g_set_object (&info->results, results);
+
+ if (info->results != NULL)
+ info->items_changed_handler =
+ g_signal_connect_object (info->results,
+ "items-changed",
+ G_CALLBACK
(gtk_source_completion_context_items_changed_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_list_model_items_changed (G_LIST_MODEL (self), position, n_removed, n_added);
+
+ break;
+ }
+
+ if (info->results != NULL)
+ position += g_list_model_get_n_items (info->results);
}
- mark_buffer = gtk_text_mark_get_buffer (context->mark);
+ gtk_source_completion_context_update_empty (self);
+}
+
+static void
+gtk_source_completion_context_populate_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GtkSourceCompletionProvider *provider = (GtkSourceCompletionProvider *)object;
+ GtkSourceCompletionContext *self;
+ CompleteTaskData *task_data;
+ g_autoptr(GListModel) results = NULL;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (G_IS_TASK (task));
+
+ self = g_task_get_source_object (task);
+ g_assert (GTK_SOURCE_IS_COMPLETION_CONTEXT (self));
+
+ task_data = g_task_get_task_data (task);
+ g_assert (task_data != NULL);
+
+ if (!(results = gtk_source_completion_provider_populate_finish (provider, result, &error)))
+ gtk_source_completion_context_mark_failed (self, provider, error);
+ else
+ gtk_source_completion_context_set_proposals_for_provider (self, provider, results);
+
+ task_data->n_active--;
+
+ gtk_source_completion_context_update_empty (self);
+
+ if (task_data->n_active == 0)
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+gtk_source_completion_context_notify_complete_cb (GtkSourceCompletionContext *self,
+ GParamSpec *pspec,
+ GTask *task)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION_CONTEXT (self));
+ g_assert (G_IS_TASK (task));
+
+ self->busy = FALSE;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
+}
- if (mark_buffer == NULL)
+/**
+ * _gtk_source_completion_context_complete_async:
+ * @self: a #GtkSourceCompletionContext
+ * @activation: how we are being activated
+ * @iter: a #GtkTextIter
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: (nullable): a callback or %NULL
+ * @user_data: closure data for @callback
+ *
+ * Asynchronously requests that the completion context load proposals
+ * from the registered providers.
+ *
+ * Since: 5.0
+ */
+void
+_gtk_source_completion_context_complete_async (GtkSourceCompletionContext *self,
+ GtkSourceCompletionActivation activation,
+ const GtkTextIter *begin,
+ const GtkTextIter *end,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+ CompleteTaskData *task_data;
+ GtkSourceBuffer *buffer;
+ guint n_items;
+
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (self));
+ g_return_if_fail (self->has_populated == FALSE);
+ g_return_if_fail (self->begin_mark == NULL);
+ g_return_if_fail (self->end_mark == NULL);
+ g_return_if_fail (begin != NULL);
+ g_return_if_fail (end != NULL);
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ self->activation = activation;
+ self->has_populated = TRUE;
+ self->busy = TRUE;
+
+ buffer = gtk_source_completion_context_get_buffer (self);
+
+ self->begin_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (buffer), NULL, begin, TRUE);
+ g_object_ref (self->begin_mark);
+
+ self->end_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (buffer), NULL, end, FALSE);
+ g_object_ref (self->end_mark);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, _gtk_source_completion_context_complete_async);
+ g_task_set_priority (task, G_PRIORITY_LOW);
+
+ task_data = g_slice_new0 (CompleteTaskData);
+ task_data->n_active = self->providers->len;
+ g_task_set_task_data (task, task_data, complete_task_data_free);
+
+ /* Always notify of busy completion, whether we fail or not */
+ g_signal_connect_object (task,
+ "notify::completed",
+ G_CALLBACK (gtk_source_completion_context_notify_complete_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ for (guint i = 0; i < self->providers->len; i++)
{
- return FALSE;
+ const ProviderInfo *info = &g_array_index (self->providers, ProviderInfo, i);
+
+ gtk_source_completion_provider_populate_async (info->provider,
+ self,
+ cancellable,
+ gtk_source_completion_context_populate_cb,
+ g_object_ref (task));
}
- view = gtk_source_completion_get_view (context->completion);
- if (view == NULL)
+ /* Providers may adjust their position based on our new marks */
+ n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
+ g_array_sort_with_data (self->providers, compare_provider_info, self);
+ g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
+
+ if (task_data->n_active == 0)
{
- return FALSE;
+ g_task_return_boolean (task, TRUE);
}
- completion_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
+}
- if (completion_buffer != mark_buffer)
+/**
+ * _gtk_source_completion_context_complete_finish:
+ * @self: an #GtkSourceCompletionContext
+ * @result: a #GAsyncResult provided to callback
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to populate proposals.
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set
+ *
+ * Since: 5.0
+ */
+gboolean
+_gtk_source_completion_context_complete_finish (GtkSourceCompletionContext *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * gtk_source_completion_context_get_busy:
+ *
+ * Gets the "busy" property. This is set to %TRUE while the completion
+ * context is actively fetching proposals from registered
+ * #GtkSourceCompletionProvider's.
+ *
+ * Returns: %TRUE if the context is busy
+ *
+ * Since: 5.0
+ */
+gboolean
+gtk_source_completion_context_get_busy (GtkSourceCompletionContext *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (self), FALSE);
+
+ return self->busy;
+}
+
+/**
+ * gtk_source_completion_context_get_buffer:
+ * @self: an #GtkSourceCompletionContext
+ *
+ * Gets the underlying buffer used by the context.
+ *
+ * This is a convenience function to get the buffer via the #GtkSourceCompletion
+ * property.
+ *
+ * Returns: (transfer none) (nullable): a #GtkTextBuffer or %NULL
+ *
+ * Since: 5.0
+ */
+GtkSourceBuffer *
+gtk_source_completion_context_get_buffer (GtkSourceCompletionContext *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (self), NULL);
+
+ if (self->completion != NULL)
+ return gtk_source_completion_get_buffer (self->completion);
+
+ return NULL;
+}
+
+/**
+ * gtk_source_completion_context_get_view:
+ * @self: a #GtkSourceCompletionContext
+ *
+ * Gets the text view for the context.
+ *
+ * Returns: (nullable) (transfer none): a #GtkSourceView or %NULL
+ *
+ * Since: 5.0
+ */
+GtkSourceView *
+gtk_source_completion_context_get_view (GtkSourceCompletionContext *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (self), NULL);
+
+ if (self->completion != NULL)
+ return gtk_source_completion_get_view (self->completion);
+
+ return NULL;
+}
+
+void
+_gtk_source_completion_context_refilter (GtkSourceCompletionContext *self)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (self));
+
+ for (guint i = 0; i < self->providers->len; i++)
{
- return FALSE;
+ const ProviderInfo *info = &g_array_index (self->providers, ProviderInfo, i);
+
+ if (info->error != NULL)
+ continue;
+
+ if (info->results == NULL)
+ continue;
+
+ gtk_source_completion_provider_refilter (info->provider, self, info->results);
}
+}
- gtk_text_buffer_get_iter_at_mark (mark_buffer, iter, context->mark);
- return TRUE;
+gboolean
+_gtk_source_completion_context_iter_invalidates (GtkSourceCompletionContext *self,
+ const GtkTextIter *iter)
+{
+ GtkTextIter begin, end;
+ GtkTextBuffer *buffer;
+
+ g_assert (!self || GTK_SOURCE_IS_COMPLETION_CONTEXT (self));
+ g_assert (iter != NULL);
+
+ if (self == NULL)
+ return FALSE;
+
+ buffer = gtk_text_iter_get_buffer (iter);
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &begin, self->begin_mark);
+ gtk_text_buffer_get_iter_at_mark (buffer, &end, self->end_mark);
+
+ return gtk_text_iter_compare (&begin, iter) <= 0 &&
+ gtk_text_iter_compare (&end, iter) >= 0;
}
/**
- * gtk_source_completion_context_get_activation:
- * @context: a #GtkSourceCompletionContext.
+ * gtk_source_completion_context_get_empty:
+ * @self: a #GtkSourceCompletionContext
+ *
+ * Checks if any proposals have been provided to the context.
*
- * Get the context activation.
+ * Out of convenience, this function will return %TRUE if @self is %NULL.
*
- * Returns: The context activation.
+ * Returns: %TRUE if there are no proposals in the context
+ *
+ * Since: 5.0
*/
-GtkSourceCompletionActivation
-gtk_source_completion_context_get_activation (GtkSourceCompletionContext *context)
+gboolean
+gtk_source_completion_context_get_empty (GtkSourceCompletionContext *self)
{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context),
- GTK_SOURCE_COMPLETION_ACTIVATION_NONE);
+ g_return_val_if_fail (!self || GTK_SOURCE_IS_COMPLETION_CONTEXT (self), FALSE);
- return context->activation;
+ return self ? self->empty : TRUE;
}
-void
-_gtk_source_completion_context_cancel (GtkSourceCompletionContext *context)
+/**
+ * gtk_source_completion_context_get_word:
+ * @self: a #GtkSourceCompletionContext
+ *
+ * Gets the word that is being completed up to the position of the insert mark.
+ *
+ * Returns: (transfer full): a string containing the current word
+ *
+ * Since: 5.0
+ */
+gchar *
+gtk_source_completion_context_get_word (GtkSourceCompletionContext *self)
{
- g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
+ GtkTextIter begin, end;
+
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (self), NULL);
+
+ gtk_source_completion_context_get_bounds (self, &begin, &end);
- g_signal_emit (context, context_signals[CANCELLED], 0);
+ return gtk_text_iter_get_slice (&begin, &end);
}
-GtkSourceCompletionContext *
-_gtk_source_completion_context_new (GtkSourceCompletion *completion,
- GtkTextIter *position)
+gboolean
+_gtk_source_completion_context_can_refilter (GtkSourceCompletionContext *self,
+ const GtkTextIter *begin,
+ const GtkTextIter *end)
{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), NULL);
- g_return_val_if_fail (position != NULL, NULL);
+ GtkTextIter old_begin;
+ GtkTextIter old_end;
- return g_object_new (GTK_SOURCE_TYPE_COMPLETION_CONTEXT,
- "completion", completion,
- "iter", position,
- NULL);
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (self), FALSE);
+ g_return_val_if_fail (begin != NULL, FALSE);
+ g_return_val_if_fail (end != NULL, FALSE);
+
+ gtk_source_completion_context_get_bounds (self, &old_begin, &old_end);
+
+ if (gtk_text_iter_equal (&old_begin, begin))
+ {
+ /*
+ * TODO: We can probably get smarter about this by asking all of
+ * the providers if they can refilter the new word (and only reload
+ * the data for those that cannot.
+ *
+ * Also, we might want to deal with that by copying the context
+ * into a new context and query using that.
+ */
+ if (gtk_text_iter_compare (&old_end, end) <= 0)
+ {
+ GtkTextBuffer *buffer = gtk_text_iter_get_buffer (begin);
+
+ gtk_text_buffer_move_mark (buffer, self->begin_mark, begin);
+ gtk_text_buffer_move_mark (buffer, self->end_mark, end);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * gtk_source_completion_context_get_activation:
+ * @self: a #GtkSourceCompletionContext
+ *
+ * Gets the mode for which the context was activated.
+ *
+ * Since: 5.0
+ */
+GtkSourceCompletionActivation
+gtk_source_completion_context_get_activation (GtkSourceCompletionContext *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (self), 0);
+
+ return self->activation;
}
diff --git a/gtksourceview/gtksourcecompletioncontext.h b/gtksourceview/gtksourcecompletioncontext.h
index f37856a5..17025749 100644
--- a/gtksourceview/gtksourcecompletioncontext.h
+++ b/gtksourceview/gtksourcecompletioncontext.h
@@ -1,7 +1,7 @@
/*
* This file is part of GtkSourceView
*
- * Copyright 2009 - Jesse van den Kieboom <jessevdk gnome org>
+ * Copyright 2020 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
@@ -15,14 +15,12 @@
*
* 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"
@@ -31,35 +29,42 @@ G_BEGIN_DECLS
#define GTK_SOURCE_TYPE_COMPLETION_CONTEXT (gtk_source_completion_context_get_type())
-GTK_SOURCE_AVAILABLE_IN_ALL
-G_DECLARE_FINAL_TYPE (GtkSourceCompletionContext, gtk_source_completion_context, GTK_SOURCE,
COMPLETION_CONTEXT, GInitiallyUnowned)
-
-/**
- * GtkSourceCompletionActivation:
- * @GTK_SOURCE_COMPLETION_ACTIVATION_NONE: None.
- * @GTK_SOURCE_COMPLETION_ACTIVATION_INTERACTIVE: Interactive activation. By
- * default, it occurs on each insertion in the #GtkTextBuffer. This can be
- * blocked temporarily with gtk_source_completion_block_interactive().
- * @GTK_SOURCE_COMPLETION_ACTIVATION_USER_REQUESTED: User requested activation.
- * By default, it occurs when the user presses
- * <keycombo><keycap>Control</keycap><keycap>space</keycap></keycombo>.
- */
typedef enum _GtkSourceCompletionActivation
{
- GTK_SOURCE_COMPLETION_ACTIVATION_NONE = 0,
- GTK_SOURCE_COMPLETION_ACTIVATION_INTERACTIVE = 1 << 0,
- GTK_SOURCE_COMPLETION_ACTIVATION_USER_REQUESTED = 1 << 1
+ GTK_SOURCE_COMPLETION_ACTIVATION_NONE = 0,
+ GTK_SOURCE_COMPLETION_ACTIVATION_INTERACTIVE = 1,
+ GTK_SOURCE_COMPLETION_ACTIVATION_USER_REQUESTED = 2,
} GtkSourceCompletionActivation;
-GTK_SOURCE_AVAILABLE_IN_ALL
-void gtk_source_completion_context_add_proposals (GtkSourceCompletionContext
*context,
- GtkSourceCompletionProvider
*provider,
- GList
*proposals,
- gboolean
finished);
-GTK_SOURCE_AVAILABLE_IN_ALL
-gboolean gtk_source_completion_context_get_iter (GtkSourceCompletionContext
*context,
- GtkTextIter
*iter);
-GTK_SOURCE_AVAILABLE_IN_ALL
-GtkSourceCompletionActivation gtk_source_completion_context_get_activation (GtkSourceCompletionContext
*context);
+GTK_SOURCE_AVAILABLE_IN_5_0
+G_DECLARE_FINAL_TYPE (GtkSourceCompletionContext, gtk_source_completion_context, GTK_SOURCE,
COMPLETION_CONTEXT, GObject)
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceCompletion *gtk_source_completion_context_get_completion
(GtkSourceCompletionContext *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceCompletionActivation gtk_source_completion_context_get_activation
(GtkSourceCompletionContext *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+gboolean gtk_source_completion_context_get_bounds
(GtkSourceCompletionContext *self,
+ GtkTextIter
*begin,
+ GtkTextIter
*end);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_context_get_start_iter
(GtkSourceCompletionContext *self,
+ GtkTextIter
*iter);
+GTK_SOURCE_AVAILABLE_IN_5_0
+gboolean gtk_source_completion_context_get_empty
(GtkSourceCompletionContext *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+char *gtk_source_completion_context_get_word
(GtkSourceCompletionContext *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+gboolean gtk_source_completion_context_get_busy
(GtkSourceCompletionContext *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceView *gtk_source_completion_context_get_view
(GtkSourceCompletionContext *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceBuffer *gtk_source_completion_context_get_buffer
(GtkSourceCompletionContext *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceLanguage *gtk_source_completion_context_get_language
(GtkSourceCompletionContext *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_context_set_proposals_for_provider
(GtkSourceCompletionContext *self,
+
GtkSourceCompletionProvider *provider,
+ GListModel
*results);
G_END_DECLS
diff --git a/gtksourceview/gtksourcecompletioninfo-private.h b/gtksourceview/gtksourcecompletioninfo-private.h
index 9017b430..2f82edbe 100644
--- a/gtksourceview/gtksourcecompletioninfo-private.h
+++ b/gtksourceview/gtksourcecompletioninfo-private.h
@@ -1,8 +1,7 @@
/*
* This file is part of GtkSourceView
*
- * Copyright 2007 - 2009 Jesús Barbero Rodríguez <chuchiperriman gmail com>
- * Copyright 2009 - Jesse van den Kieboom <jessevdk gnome org>
+ * Copyright 2020 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
@@ -16,16 +15,23 @@
*
* 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 "gtksourcecompletioninfo.h"
+#include <gtk/gtk.h>
+
+#include "gtksourcetypes-private.h"
G_BEGIN_DECLS
-G_GNUC_INTERNAL
-void _gtk_source_completion_info_set_xoffset (GtkSourceCompletionInfo *info,
- gint xoffset);
+#define GTK_SOURCE_TYPE_COMPLETION_INFO (_gtk_source_completion_info_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkSourceCompletionInfo, _gtk_source_completion_info, GTK_SOURCE, COMPLETION_INFO,
GtkSourceAssistant)
+
+GtkSourceCompletionInfo *_gtk_source_completion_info_new (void);
+GtkSourceCompletionCell *_gtk_source_completion_info_get_cell (GtkSourceCompletionInfo *self);
G_END_DECLS
diff --git a/gtksourceview/gtksourcecompletioninfo.c b/gtksourceview/gtksourcecompletioninfo.c
index fc05baa4..405ac3fa 100644
--- a/gtksourceview/gtksourcecompletioninfo.c
+++ b/gtksourceview/gtksourcecompletioninfo.c
@@ -1,9 +1,7 @@
/*
* This file is part of GtkSourceView
*
- * Copyright 2007-2009 Jesús Barbero Rodríguez <chuchiperriman gmail com>
- * Copyright 2009 Jesse van den Kieboom <jessevdk gnome org>
- * Copyright 2012 Sébastien Wilmet <swilmet gnome org>
+ * Copyright 2020 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
@@ -17,286 +15,74 @@
*
* 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/>.
- */
-
-/**
- * SECTION:completioninfo
- * @title: GtkSourceCompletionInfo
- * @short_description: Calltips object
- *
- * This object can be used to show a calltip or help for the
- * current completion proposal.
- *
- * The info window has always the same size as the natural size of its child
- * widget, added with gtk_widget_set_child(). If you want a fixed size instead, a
- * possibility is to use a scrolled window, as the following example
- * demonstrates.
*
- * <example>
- * <title>Fixed size with a scrolled window.</title>
- * <programlisting>
- * GtkSourceCompletionInfo *info;
- * GtkWidget *your_widget;
- * GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL);
- *
- * gtk_widget_set_size_request (scrolled_window, 300, 200);
- * gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled_window), your_widget);
- * gtk_window_set_child (GTK_WINDOW (info), scrolled_window);
- * </programlisting>
- * </example>
+ * SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
-#include <glib/gi18n-lib.h>
-
+#include "gtksourceassistant-private.h"
+#include "gtksourcecompletioncell-private.h"
#include "gtksourcecompletioninfo-private.h"
struct _GtkSourceCompletionInfo
{
- GtkWindow parent_instance;
-
- guint idle_resize;
-
- GtkWidget *attached_to;
- GtkEventController *focus;
- gulong focus_out_event_handler;
-
- gint xoffset;
-
- guint transient_set : 1;
+ GtkSourceAssistant parent_instance;
+ GtkSourceCompletionCell *cell;
};
-G_DEFINE_TYPE (GtkSourceCompletionInfo, gtk_source_completion_info, GTK_TYPE_WINDOW);
-
-/* Resize the window */
-
-/* Init, dispose, finalize, ... */
+G_DEFINE_TYPE (GtkSourceCompletionInfo, _gtk_source_completion_info, GTK_SOURCE_TYPE_ASSISTANT)
static void
-set_attached_to (GtkSourceCompletionInfo *info,
- GtkWidget *attached_to)
+_gtk_source_completion_info_get_offset (GtkSourceAssistant *assistant,
+ int *x_offset,
+ int *y_offset)
{
- if (info->attached_to == attached_to)
- {
- return;
- }
-
- if (info->attached_to != NULL)
- {
- g_object_remove_weak_pointer (G_OBJECT (info->attached_to),
- (gpointer *) &info->attached_to);
-
- if (info->focus_out_event_handler != 0)
- {
- g_signal_handler_disconnect (info->focus,
- info->focus_out_event_handler);
-
- info->focus_out_event_handler = 0;
- info->focus = NULL;
- }
- }
-
- info->attached_to = attached_to;
- info->focus = NULL;
+ GtkStyleContext *style_context;
+ GtkBorder margin;
- if (attached_to == NULL)
- {
- return;
- }
+ g_assert (GTK_SOURCE_IS_COMPLETION_INFO (assistant));
- g_object_add_weak_pointer (G_OBJECT (attached_to),
- (gpointer *) &info->attached_to);
+ style_context = gtk_widget_get_style_context (GTK_WIDGET (assistant));
+ gtk_style_context_get_margin (style_context, &margin);
- info->focus = gtk_event_controller_focus_new ();
- gtk_widget_add_controller (GTK_WIDGET (attached_to), info->focus);
-
- info->focus_out_event_handler =
- g_signal_connect_swapped (info->focus,
- "leave",
- G_CALLBACK (gtk_widget_hide),
- info);
-
- info->transient_set = FALSE;
+ *x_offset = -margin.left + 1;
+ *y_offset = -margin.top;
}
static void
-gtk_source_completion_info_init (GtkSourceCompletionInfo *info)
+_gtk_source_completion_info_class_init (GtkSourceCompletionInfoClass *klass)
{
- /* Tooltip style */
- gtk_window_set_title (GTK_WINDOW (info), _("Completion Info"));
- gtk_widget_set_name (GTK_WIDGET (info), "gtk-tooltip");
+ GtkSourceAssistantClass *assistant_class = GTK_SOURCE_ASSISTANT_CLASS (klass);
- g_object_set (info,
- "margin-top", 1,
- "margin-bottom", 1,
- "margin-start", 1,
- "margin-end", 1,
- NULL);
+ assistant_class->get_offset = _gtk_source_completion_info_get_offset;
}
static void
-gtk_source_completion_info_dispose (GObject *object)
+_gtk_source_completion_info_init (GtkSourceCompletionInfo *self)
{
- GtkSourceCompletionInfo *info = GTK_SOURCE_COMPLETION_INFO (object);
-
- if (info->idle_resize != 0)
- {
- g_source_remove (info->idle_resize);
- info->idle_resize = 0;
- }
-
- set_attached_to (info, NULL);
-
- G_OBJECT_CLASS (gtk_source_completion_info_parent_class)->dispose (object);
+ gtk_widget_add_css_class (GTK_WIDGET (self), "completion-info");
+ gtk_popover_set_position (GTK_POPOVER (self), GTK_POS_RIGHT);
+ gtk_popover_set_autohide (GTK_POPOVER (self), FALSE);
+
+ self->cell = g_object_new (GTK_SOURCE_TYPE_COMPLETION_CELL,
+ "column", GTK_SOURCE_COMPLETION_COLUMN_DETAILS,
+ "halign", GTK_ALIGN_START,
+ "valign", GTK_ALIGN_START,
+ NULL);
+ _gtk_source_assistant_set_child (GTK_SOURCE_ASSISTANT (self), GTK_WIDGET (self->cell));
}
-static void
-gtk_source_completion_info_show (GtkWidget *widget)
-{
- GtkSourceCompletionInfo *info = GTK_SOURCE_COMPLETION_INFO (widget);
-
- if (info->attached_to != NULL && !info->transient_set)
- {
- GtkRoot *toplevel;
-
- toplevel = gtk_widget_get_root (GTK_WIDGET (info->attached_to));
-
- if (toplevel != NULL)
- {
- gtk_window_set_transient_for (GTK_WINDOW (info),
- GTK_WINDOW (toplevel));
- info->transient_set = TRUE;
- }
- }
-
- GTK_WIDGET_CLASS (gtk_source_completion_info_parent_class)->show (widget);
-}
-
-static void
-gtk_source_completion_info_class_init (GtkSourceCompletionInfoClass *klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
- GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
-
- object_class->dispose = gtk_source_completion_info_dispose;
-
- widget_class->show = gtk_source_completion_info_show;
-}
-
-void
-_gtk_source_completion_info_set_xoffset (GtkSourceCompletionInfo *window,
- gint xoffset)
-{
- g_return_if_fail (GTK_SOURCE_IS_COMPLETION_INFO (window));
-
- window->xoffset = xoffset;
-}
-
-static void
-move_to_iter (GtkSourceCompletionInfo *window,
- GtkTextView *view,
- GtkTextIter *iter)
-{
- GdkRectangle location;
- GdkPopupLayout *layout;
- GdkSurface *surface;
- GtkRoot *root;
- gdouble x;
- gdouble y;
-
- if (!GTK_IS_NATIVE (window))
- return;
-
- surface = gtk_native_get_surface (GTK_NATIVE (window));
- if (surface == NULL)
- return;
-
- root = gtk_widget_get_root (GTK_WIDGET (view));
- if (root == NULL)
- return;
-
- gtk_text_view_get_iter_location (view, iter, &location);
- gtk_text_view_buffer_to_window_coords (view,
- GTK_TEXT_WINDOW_WIDGET,
- location.x,
- location.y,
- &location.x,
- &location.y);
-
- gtk_widget_translate_coordinates (GTK_WIDGET (view),
- GTK_WIDGET (root),
- location.x,
- location.y,
- &x,
- &y);
-
- location.x = x;
- location.y = y;
-
- layout = gdk_popup_layout_new (&location, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST);
- gdk_popup_layout_set_anchor_hints (layout, GDK_ANCHOR_SLIDE | GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_RESIZE);
- gdk_popup_layout_set_offset (layout, window->xoffset, 0);
- gdk_popup_present (GDK_POPUP (surface), 0, 0, layout);
- gdk_popup_layout_unref (layout);
-}
-
-static void
-move_to_cursor (GtkSourceCompletionInfo *window,
- GtkTextView *view)
-{
- GtkTextBuffer *buffer;
- GtkTextIter insert;
-
- buffer = gtk_text_view_get_buffer (view);
- gtk_text_buffer_get_iter_at_mark (buffer, &insert, gtk_text_buffer_get_insert (buffer));
-
- move_to_iter (window, view, &insert);
-}
-
-/* Public functions */
-
-/**
- * gtk_source_completion_info_new:
- *
- * Returns: a new GtkSourceCompletionInfo.
- */
GtkSourceCompletionInfo *
-gtk_source_completion_info_new (void)
+_gtk_source_completion_info_new (void)
{
- return g_object_new (GTK_SOURCE_TYPE_COMPLETION_INFO,
- "margin-top", 3,
- "margin-bottom", 3,
- "margin-start", 3,
- "margin-end", 3,
- NULL);
+ return g_object_new (GTK_SOURCE_TYPE_COMPLETION_INFO, NULL);
}
-/**
- * gtk_source_completion_info_move_to_iter:
- * @info: a #GtkSourceCompletionInfo.
- * @view: a #GtkTextView on which the info window should be positioned.
- * @iter: (nullable): a #GtkTextIter.
- *
- * Moves the #GtkSourceCompletionInfo to @iter. If @iter is %NULL @info is
- * moved to the cursor position. Moving will respect the #GdkGravity setting
- * of the info window and will ensure the line at @iter is not occluded by
- * the window.
- */
-void
-gtk_source_completion_info_move_to_iter (GtkSourceCompletionInfo *info,
- GtkTextView *view,
- GtkTextIter *iter)
+GtkSourceCompletionCell *
+_gtk_source_completion_info_get_cell (GtkSourceCompletionInfo *self)
{
- g_return_if_fail (GTK_SOURCE_IS_COMPLETION_INFO (info));
- g_return_if_fail (GTK_IS_TEXT_VIEW (view));
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_INFO (self), NULL);
- if (iter == NULL)
- {
- move_to_cursor (info, view);
- }
- else
- {
- move_to_iter (info, view, iter);
- }
+ return self->cell;
}
diff --git a/gtksourceview/gtksourcecompletionlist-private.h b/gtksourceview/gtksourcecompletionlist-private.h
new file mode 100644
index 00000000..1b7f06fa
--- /dev/null
+++ b/gtksourceview/gtksourcecompletionlist-private.h
@@ -0,0 +1,48 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 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 <gtk/gtk.h>
+
+#include "gtksourceassistant-private.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_COMPLETION_LIST (_gtk_source_completion_list_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkSourceCompletionList, _gtk_source_completion_list, GTK_SOURCE, COMPLETION_LIST,
GtkSourceAssistant)
+
+GtkSourceCompletionList *_gtk_source_completion_list_new (void);
+void _gtk_source_completion_list_reposition (GtkSourceCompletionList *self);
+GtkSourceCompletionContext *_gtk_source_completion_list_get_context (GtkSourceCompletionList *self);
+void _gtk_source_completion_list_set_context (GtkSourceCompletionList *self,
+ GtkSourceCompletionContext
*context);
+gboolean _gtk_source_completion_list_get_show_details (GtkSourceCompletionList *self);
+void _gtk_source_completion_list_set_show_details (GtkSourceCompletionList *self,
+ gboolean
show_details);
+guint _gtk_source_completion_list_get_n_rows (GtkSourceCompletionList *self);
+void _gtk_source_completion_list_set_n_rows (GtkSourceCompletionList *self,
+ guint
n_rows);
+void _gtk_source_completion_list_set_font_desc (GtkSourceCompletionList *self,
+ const PangoFontDescription
*font_desc);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcecompletionlist.c b/gtksourceview/gtksourcecompletionlist.c
new file mode 100644
index 00000000..64f69727
--- /dev/null
+++ b/gtksourceview/gtksourcecompletionlist.c
@@ -0,0 +1,563 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 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 <glib/gi18n.h>
+
+#include "gtksourcecompletioncell-private.h"
+#include "gtksourcecompletioncontext-private.h"
+#include "gtksourcecompletionlist-private.h"
+#include "gtksourcecompletionlistbox-private.h"
+#include "gtksourcecompletionlistboxrow-private.h"
+#include "gtksourcecompletioninfo-private.h"
+#include "gtksourcecompletionprovider.h"
+#include "gtksourceutils-private.h"
+#include "gtksourceview.h"
+
+struct _GtkSourceCompletionList
+{
+ GtkSourceAssistant parent_instance;
+
+ GtkSourceCompletionContext *context;
+ GtkSourceCompletionInfo *info;
+
+ /* Template Widgets */
+ GtkSourceCompletionListBox *listbox;
+ GtkScrolledWindow *scroller;
+ GtkToggleButton *show_details;
+ GtkBox *details;
+ GtkSourceCompletionCell *comments;
+ GtkLabel *alternate_label;
+};
+
+enum {
+ PROP_0,
+ PROP_CONTEXT,
+ PROP_SHOW_DETAILS,
+ N_PROPS
+};
+
+G_DEFINE_TYPE (GtkSourceCompletionList, _gtk_source_completion_list, GTK_SOURCE_TYPE_ASSISTANT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+_gtk_source_completion_list_show (GtkWidget *widget)
+{
+ GtkSourceCompletionList *self = (GtkSourceCompletionList *)widget;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST (self));
+
+ _gtk_source_completion_list_reposition (self);
+
+ GTK_WIDGET_CLASS (_gtk_source_completion_list_parent_class)->show (widget);
+
+ if (_gtk_source_completion_list_get_show_details (self))
+ {
+ gtk_widget_show (GTK_WIDGET (self->info));
+ }
+}
+
+static void
+_gtk_source_completion_list_show_details_notify_active_cb (GtkSourceCompletionList *self,
+ GParamSpec *pspec,
+ GtkToggleButton *toggle)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST (self));
+ g_assert (pspec != NULL);
+ g_assert (GTK_IS_TOGGLE_BUTTON (toggle));
+
+ if (gtk_widget_get_visible (GTK_WIDGET (self)) &&
+ _gtk_source_completion_list_get_show_details (self))
+ {
+ gtk_widget_show (GTK_WIDGET (self->info));
+ }
+ else
+ {
+ gtk_widget_hide (GTK_WIDGET (self->info));
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_DETAILS]);
+}
+
+static void
+_gtk_source_completion_list_update_comment (GtkSourceCompletionList *self)
+{
+ GtkSourceCompletionCell *info = NULL;
+ GtkSourceCompletionProposal *proposal = NULL;
+ GtkSourceCompletionProvider *provider = NULL;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST (self));
+
+ if (self->info != NULL)
+ {
+ info = _gtk_source_completion_info_get_cell (self->info);
+ }
+
+ if (_gtk_source_completion_list_box_get_selected (self->listbox, &provider, &proposal))
+ {
+ gtk_source_completion_provider_display (provider, self->context, proposal, self->comments);
+
+ if (info != NULL)
+ {
+ gtk_source_completion_provider_display (provider, self->context, proposal, info);
+ }
+ }
+ else
+ {
+ gtk_source_completion_cell_set_widget (self->comments, NULL);
+
+ if (info != NULL)
+ {
+ gtk_source_completion_cell_set_widget (info, NULL);
+ }
+ }
+
+ if (_gtk_source_completion_cell_is_empty (self->comments) &&
+ (info == NULL || _gtk_source_completion_cell_is_empty (info)))
+ {
+ gtk_widget_hide (GTK_WIDGET (self->details));
+ }
+ else
+ {
+ gtk_widget_show (GTK_WIDGET (self->details));
+ }
+
+ if (info != NULL)
+ {
+ if (_gtk_source_completion_cell_is_empty (info))
+ {
+ gtk_widget_hide (GTK_WIDGET (self->info));
+ }
+ else if (_gtk_source_completion_list_get_show_details (self))
+ {
+ if (gtk_widget_get_visible (GTK_WIDGET (self)))
+ {
+ gtk_widget_show (GTK_WIDGET (self->info));
+ }
+ }
+ }
+
+ g_clear_object (&proposal);
+ g_clear_object (&provider);
+}
+
+static void
+_gtk_source_completion_list_notify_alternates_cb (GtkSourceCompletionList *self,
+ GParamSpec *pspec,
+ GtkSourceCompletionListBox *listbox)
+{
+ int n_alternates;
+ int alternate;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST (self));
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (listbox));
+
+ n_alternates = _gtk_source_completion_list_box_get_n_alternates (listbox);
+ alternate = _gtk_source_completion_list_box_get_alternate (listbox);
+
+ if (n_alternates == 0)
+ {
+ gtk_label_set_label (self->alternate_label, NULL);
+ }
+ else
+ {
+ char *str;
+
+ n_alternates++;
+ alternate = _gtk_source_completion_list_box_get_alternate (self->listbox);
+
+ if (alternate == -1)
+ {
+ str = g_strdup_printf (_("%d of %u"), 1, n_alternates);
+ }
+ else
+ {
+ str = g_strdup_printf (_("%d of %u"), alternate + 1, n_alternates);
+ }
+
+ gtk_label_set_label (self->alternate_label, str);
+
+ g_free (str);
+ }
+}
+
+static void
+_gtk_source_completion_list_notify_proposal (GtkSourceCompletionList *self,
+ GParamSpec *pspec,
+ GtkSourceCompletionListBox *listbox)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST (self));
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (listbox));
+
+ _gtk_source_completion_list_update_comment (self);
+ _gtk_source_completion_list_notify_alternates_cb (self, NULL, listbox);
+}
+
+static void
+_gtk_source_completion_list_get_offset (GtkSourceAssistant *assistant,
+ int *x_offset,
+ int *y_offset)
+{
+ GtkSourceCompletionList *self = (GtkSourceCompletionList *)assistant;
+ GtkSourceCompletionListBoxRow *row;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST (self));
+ g_assert (x_offset != NULL);
+ g_assert (y_offset != NULL);
+
+ GTK_SOURCE_ASSISTANT_CLASS (_gtk_source_completion_list_parent_class)->get_offset (assistant,
x_offset, y_offset);
+
+ if ((row = _gtk_source_completion_list_box_get_first (self->listbox)))
+ {
+ *x_offset = _gtk_source_completion_list_box_row_get_x_offset (row, GTK_WIDGET (self));
+ }
+}
+
+static gboolean
+key_press_propagate_cb (GtkSourceCompletionList *self,
+ guint keyval,
+ guint keycode,
+ GdkModifierType modifiers,
+ GtkEventControllerKey *key)
+{
+ GtkWidget *parent;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST (self));
+ g_assert (GTK_IS_EVENT_CONTROLLER_KEY (key));
+
+ if (gtk_event_controller_key_forward (key, GTK_WIDGET (self->listbox)))
+ {
+ return TRUE;
+ }
+
+ parent = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_SOURCE_TYPE_VIEW);
+
+ if (GTK_SOURCE_IS_VIEW (parent))
+ {
+ return gtk_event_controller_key_forward (key, parent);
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+key_release_propagate_cb (GtkSourceCompletionList *self,
+ guint keyval,
+ guint keycode,
+ GdkModifierType modifiers,
+ GtkEventControllerKey *key)
+{
+ GtkWidget *parent;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST (self));
+ g_assert (GTK_IS_EVENT_CONTROLLER_KEY (key));
+
+ if (gtk_event_controller_key_forward (key, GTK_WIDGET (self->listbox)))
+ {
+ return TRUE;
+ }
+
+ parent = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_SOURCE_TYPE_VIEW);
+
+ if (GTK_SOURCE_IS_VIEW (parent))
+ {
+ return gtk_event_controller_key_forward (key, parent);
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+_gtk_source_completion_list_get_target_location (GtkSourceAssistant *assistant,
+ GdkRectangle *rect)
+{
+ g_assert (GTK_SOURCE_IS_ASSISTANT (assistant));
+ g_assert (rect != NULL);
+
+ GTK_SOURCE_ASSISTANT_CLASS (_gtk_source_completion_list_parent_class)->get_target_location
(assistant, rect);
+
+ /* We want to align to the beginning of the character, so set the width
+ * to 0 to ensure things align correctly.
+ */
+ rect->width = 0;
+}
+
+static GtkSizeRequestMode
+_gtk_source_completion_list_get_request_mode (GtkWidget *widget)
+{
+ return GTK_SIZE_REQUEST_CONSTANT_SIZE;
+}
+
+static void
+_gtk_source_completion_list_dispose (GObject *object)
+{
+ GtkSourceCompletionList *self = (GtkSourceCompletionList *)object;
+
+ g_clear_object (&self->context);
+
+ G_OBJECT_CLASS (_gtk_source_completion_list_parent_class)->dispose (object);
+}
+
+static void
+_gtk_source_completion_list_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkSourceCompletionList *self = GTK_SOURCE_COMPLETION_LIST (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONTEXT:
+ g_value_set_object (value, self->context);
+ break;
+
+ case PROP_SHOW_DETAILS:
+ g_value_set_boolean (value, _gtk_source_completion_list_get_show_details (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+_gtk_source_completion_list_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkSourceCompletionList *self = GTK_SOURCE_COMPLETION_LIST (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONTEXT:
+ _gtk_source_completion_list_set_context (self, g_value_get_object (value));
+ break;
+
+ case PROP_SHOW_DETAILS:
+ _gtk_source_completion_list_set_show_details (self, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+_gtk_source_completion_list_class_init (GtkSourceCompletionListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkSourceAssistantClass *assistant_class = GTK_SOURCE_ASSISTANT_CLASS (klass);
+
+ object_class->dispose = _gtk_source_completion_list_dispose;
+ object_class->get_property = _gtk_source_completion_list_get_property;
+ object_class->set_property = _gtk_source_completion_list_set_property;
+
+ widget_class->get_request_mode = _gtk_source_completion_list_get_request_mode;
+ widget_class->show = _gtk_source_completion_list_show;
+
+ assistant_class->get_offset = _gtk_source_completion_list_get_offset;
+ assistant_class->get_target_location = _gtk_source_completion_list_get_target_location;
+
+ properties [PROP_CONTEXT] =
+ g_param_spec_object ("context",
+ "Context",
+ "The context containing results",
+ GTK_SOURCE_TYPE_COMPLETION_CONTEXT,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_SHOW_DETAILS] =
+ g_param_spec_boolean ("show-details",
+ "Show Details",
+ "Show the details assistant",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/gtksourceview/gtksourcecompletionlist.ui");
+ gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionList, alternate_label);
+ gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionList, comments);
+ gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionList, details);
+ gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionList, listbox);
+ gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionList, scroller);
+ gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionList, show_details);
+ gtk_widget_class_bind_template_callback (widget_class, _gtk_source_completion_list_reposition);
+ gtk_widget_class_bind_template_callback (widget_class, _gtk_source_completion_list_notify_proposal);
+
+ g_type_ensure (GTK_SOURCE_TYPE_COMPLETION_LIST_BOX);
+}
+
+static void
+_gtk_source_completion_list_init (GtkSourceCompletionList *self)
+{
+ GtkEventController *key;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+ gtk_widget_add_css_class (GTK_WIDGET (self), "completion");
+ gtk_popover_set_position (GTK_POPOVER (self), GTK_POS_BOTTOM);
+
+ key = gtk_event_controller_key_new ();
+ gtk_event_controller_set_propagation_phase (key, GTK_PHASE_BUBBLE);
+ g_signal_connect_object (key,
+ "key-pressed",
+ G_CALLBACK (key_press_propagate_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (key,
+ "key-released",
+ G_CALLBACK (key_release_propagate_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ gtk_widget_add_controller (GTK_WIDGET (self), g_steal_pointer (&key));
+
+ self->info = GTK_SOURCE_COMPLETION_INFO (_gtk_source_completion_info_new ());
+ _gtk_source_assistant_attach (GTK_SOURCE_ASSISTANT (self->info),
+ GTK_SOURCE_ASSISTANT (self));
+
+ g_signal_connect_object (self->show_details,
+ "notify::active",
+ G_CALLBACK (_gtk_source_completion_list_show_details_notify_active_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->listbox,
+ "notify::alternate",
+ G_CALLBACK (_gtk_source_completion_list_notify_alternates_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->listbox,
+ "notify::n-alternates",
+ G_CALLBACK (_gtk_source_completion_list_notify_alternates_cb),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+GtkSourceCompletionList *
+_gtk_source_completion_list_new (void)
+{
+ return g_object_new (GTK_SOURCE_TYPE_COMPLETION_LIST, NULL);
+}
+
+GtkSourceCompletionContext *
+_gtk_source_completion_list_get_context (GtkSourceCompletionList *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST (self), NULL);
+
+ return self->context;
+}
+
+void
+_gtk_source_completion_list_set_context (GtkSourceCompletionList *self,
+ GtkSourceCompletionContext *context)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST (self));
+ g_return_if_fail (!context || GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
+
+ if (g_set_object (&self->context, context))
+ {
+ _gtk_source_completion_list_box_set_context (self->listbox, context);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONTEXT]);
+ }
+}
+
+gboolean
+_gtk_source_completion_list_get_show_details (GtkSourceCompletionList *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST (self), FALSE);
+
+ return gtk_toggle_button_get_active (self->show_details);
+}
+
+void
+_gtk_source_completion_list_set_show_details (GtkSourceCompletionList *self,
+ gboolean show_details)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST (self));
+
+ gtk_toggle_button_set_active (self->show_details, show_details);
+}
+
+guint
+_gtk_source_completion_list_get_n_rows (GtkSourceCompletionList *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST (self), 0);
+
+ return _gtk_source_completion_list_box_get_n_rows (self->listbox);
+}
+
+void
+_gtk_source_completion_list_set_n_rows (GtkSourceCompletionList *self,
+ guint n_rows)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST (self));
+
+ _gtk_source_completion_list_box_set_n_rows (self->listbox, n_rows);
+}
+
+void
+_gtk_source_completion_list_set_font_desc (GtkSourceCompletionList *self,
+ const PangoFontDescription *font_desc)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST (self));
+
+ _gtk_source_completion_list_box_set_font_desc (self->listbox, font_desc);
+}
+
+void
+_gtk_source_completion_list_reposition (GtkSourceCompletionList *self)
+{
+ int old_x_offset, old_y_offset;
+ int x_offset = 0, y_offset = 0;
+ int min_width, nat_width;
+
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST (self));
+
+ /* Hack to force the sizing request, otherwise the popover does
+ * not shrink as results are reduced.
+ */
+ gtk_widget_set_size_request (GTK_WIDGET (self), -1, -1);
+ gtk_widget_measure (GTK_WIDGET (self),
+ GTK_ORIENTATION_HORIZONTAL,
+ -1,
+ &min_width, &nat_width, NULL, NULL);
+ gtk_widget_set_size_request (GTK_WIDGET (self), min_width, -1);
+
+ /* Reposition the popup if we have a new offset */
+ _gtk_source_completion_list_get_offset (GTK_SOURCE_ASSISTANT (self), &x_offset, &y_offset);
+ gtk_popover_get_offset (GTK_POPOVER (self), &old_x_offset, &old_y_offset);
+ if (old_x_offset != x_offset || old_y_offset != y_offset)
+ {
+ gtk_popover_set_offset (GTK_POPOVER (self), x_offset, y_offset);
+ }
+
+ gtk_native_check_resize (GTK_NATIVE (self));
+
+ if (gtk_widget_get_visible (GTK_WIDGET (self->info)))
+ {
+ gtk_native_check_resize (GTK_NATIVE (self->info));
+ }
+}
diff --git a/gtksourceview/gtksourcecompletionlist.ui b/gtksourceview/gtksourcecompletionlist.ui
new file mode 100644
index 00000000..70d76ccb
--- /dev/null
+++ b/gtksourceview/gtksourcecompletionlist.ui
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="GtkSourceCompletionList" parent="GtkSourceAssistant">
+ <property name="can-focus">false</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="can-focus">false</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scroller">
+ <property name="hscrollbar-policy">never</property>
+ <property name="vscrollbar-policy">automatic</property>
+ <property name="propagate-natural-width">true</property>
+ <property name="propagate-natural-height">true</property>
+ <property name="min-content-height">1</property>
+ <child>
+ <object class="GtkSourceCompletionListBox" id="listbox">
+ <property name="can-focus">false</property>
+ <signal name="notify::proposal" handler="_gtk_source_completion_list_notify_proposal"
swapped="true"/>
+ <signal name="reposition" handler="_gtk_source_completion_list_reposition" swapped="true"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="details">
+ <property name="can-focus">false</property>
+ <property name="halign">fill</property>
+ <property name="hexpand">true</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">12</property>
+ <property name="vexpand">true</property>
+ <style>
+ <class name="details"/>
+ </style>
+ <child>
+ <object class="GtkSourceCompletionCell" id="comments">
+ <property name="column">comment</property>
+ <property name="hexpand">true</property>
+ <property name="valign">baseline</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="baseline-position">top</property>
+ <property name="valign">baseline</property>
+ <child>
+ <object class="GtkLabel" id="alternate_label">
+ <property name="margin-end">6</property>
+ <property name="margin-start">6</property>
+ <property name="valign">baseline</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="show_details">
+ <property name="can-focus">false</property>
+ <property name="label" translatable="yes">_Details</property>
+ <property name="use-underline">true</property>
+ <property name="halign">end</property>
+ <property name="valign">start</property>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/gtksourceview/gtksourcecompletionlistbox-private.h
b/gtksourceview/gtksourcecompletionlistbox-private.h
new file mode 100644
index 00000000..16dcacbf
--- /dev/null
+++ b/gtksourceview/gtksourcecompletionlistbox-private.h
@@ -0,0 +1,56 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 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 <gtk/gtk.h>
+
+#include "gtksourcetypes-private.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_COMPLETION_LIST_BOX (gtk_source_completion_list_box_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkSourceCompletionListBox, gtk_source_completion_list_box, GTK_SOURCE,
COMPLETION_LIST_BOX, GtkWidget)
+
+GtkWidget *_gtk_source_completion_list_box_new (void);
+GtkSourceCompletionContext *_gtk_source_completion_list_box_get_context (GtkSourceCompletionListBox
*self);
+void _gtk_source_completion_list_box_set_context (GtkSourceCompletionListBox
*self,
+ GtkSourceCompletionContext
*context);
+guint _gtk_source_completion_list_box_get_n_rows (GtkSourceCompletionListBox
*self);
+void _gtk_source_completion_list_box_set_n_rows (GtkSourceCompletionListBox
*self,
+ guint
n_rows);
+GtkSourceCompletionProposal *_gtk_source_completion_list_box_get_proposal (GtkSourceCompletionListBox
*self);
+gboolean _gtk_source_completion_list_box_get_selected (GtkSourceCompletionListBox
*self,
+ GtkSourceCompletionProvider
**provider,
+ GtkSourceCompletionProposal
**proposal);
+void _gtk_source_completion_list_box_move_cursor (GtkSourceCompletionListBox
*self,
+ GtkMovementStep
step,
+ gint
direction);
+void _gtk_source_completion_list_box_set_font_desc (GtkSourceCompletionListBox
*self,
+ const PangoFontDescription
*font_desc);
+GtkSourceCompletionListBoxRow *_gtk_source_completion_list_box_get_first (GtkSourceCompletionListBox
*self);
+gboolean _gtk_source_completion_list_box_key_activates (GtkSourceCompletionListBox
*self,
+ const GdkKeyEvent
*key);
+int _gtk_source_completion_list_box_get_alternate (GtkSourceCompletionListBox
*self);
+guint _gtk_source_completion_list_box_get_n_alternates (GtkSourceCompletionListBox
*self);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcecompletionlistbox.c b/gtksourceview/gtksourcecompletionlistbox.c
new file mode 100644
index 00000000..07301719
--- /dev/null
+++ b/gtksourceview/gtksourcecompletionlistbox.c
@@ -0,0 +1,1265 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 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 "gtksourcecompletion-private.h"
+#include "gtksourcecompletioncontext-private.h"
+#include "gtksourcecompletionlistbox-private.h"
+#include "gtksourcecompletionlistboxrow-private.h"
+#include "gtksourcecompletionproposal.h"
+#include "gtksourcecompletionprovider.h"
+
+struct _GtkSourceCompletionListBox
+{
+ GtkWidget parent_instance;
+
+ /* The box containing the rows. */
+ GtkBox *box;
+
+ /* Font styling for rows */
+ PangoAttrList *font_attrs;
+
+ /* The completion context that is being displayed. */
+ GtkSourceCompletionContext *context;
+
+ /* The handler for GtkSourceCompletionContext::items-chaged which should
+ * be disconnected when no longer needed.
+ */
+ gulong items_changed_handler;
+
+ /* The number of rows we expect to have visible to the user. */
+ guint n_rows;
+
+ /* The currently selected index within the result set. Signed to
+ * ensure our math in various places allows going negative to catch
+ * lower edge.
+ */
+ int selected;
+
+ /* The alternate proposal for the current selection. This references
+ * something from GtkSourceCompletionProvider.list_alternates().
+ */
+ GPtrArray *alternates;
+ int alternate;
+
+ /* This is set whenever we make a change that requires updating the
+ * row content. We delay the update until the next frame callback so
+ * that we only update once right before we draw the frame. This helps
+ * reduce duplicate work when reacting to ::items-changed in the model.
+ */
+ guint queued_update;
+
+ /* These size groups are used to keep each portion of the proposal row
+ * aligned with each other. Since we only have a fixed number of visible
+ * rows, the overhead here is negligible by introducing the size cycle.
+ */
+ GtkSizeGroup *before_size_group;
+ GtkSizeGroup *typed_text_size_group;
+ GtkSizeGroup *after_size_group;
+
+ /* The adjustments for scrolling the GtkScrollable. */
+ GtkAdjustment *hadjustment;
+ GtkAdjustment *vadjustment;
+
+ /* Gesture to handle button press/touch events. */
+ GtkGesture *click_gesture;
+};
+
+typedef struct
+{
+ GtkSourceCompletionListBox *self;
+ GtkSourceCompletionContext *context;
+ guint n_items;
+ guint position;
+ int selected;
+} UpdateState;
+
+enum {
+ PROP_0,
+ PROP_ALTERNATE,
+ PROP_CONTEXT,
+ PROP_PROPOSAL,
+ PROP_N_ROWS,
+ PROP_HADJUSTMENT,
+ PROP_HSCROLL_POLICY,
+ PROP_N_ALTERNATES,
+ PROP_VADJUSTMENT,
+ PROP_VSCROLL_POLICY,
+ N_PROPS
+};
+
+enum {
+ REPOSITION,
+ N_SIGNALS
+};
+
+static void gtk_source_completion_list_box_queue_update (GtkSourceCompletionListBox *self);
+static gboolean gtk_source_completion_list_box_update_cb (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ gpointer user_data);
+static void gtk_source_completion_list_box_do_update (GtkSourceCompletionListBox *self,
+ gboolean update_selection);
+
+G_DEFINE_TYPE_WITH_CODE (GtkSourceCompletionListBox, gtk_source_completion_list_box, GTK_TYPE_WIDGET,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
+static void
+gtk_source_completion_list_box_set_selected (GtkSourceCompletionListBox *self,
+ int selected)
+{
+ GtkSourceCompletionProposal *proposal = NULL;
+ GtkSourceCompletionProvider *provider = NULL;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+
+ if (selected == -1 && self->context != NULL)
+ {
+ GtkSourceCompletion *completion;
+ gboolean select_on_show;
+
+ completion = gtk_source_completion_context_get_completion (self->context);
+ select_on_show = _gtk_source_completion_get_select_on_show (completion);
+
+ if (select_on_show)
+ {
+ selected = 0;
+ }
+ }
+
+ self->selected = selected;
+ self->alternate = -1;
+ g_clear_pointer (&self->alternates, g_ptr_array_unref);
+
+ if (_gtk_source_completion_list_box_get_selected (self, &provider, &proposal))
+ {
+ self->alternates = gtk_source_completion_provider_list_alternates (provider, self->context,
proposal);
+
+ if (self->alternates != NULL)
+ {
+ g_ptr_array_set_free_func (self->alternates, g_object_unref);
+ }
+ }
+
+ gtk_source_completion_list_box_queue_update (self);
+
+ g_clear_object (&proposal);
+ g_clear_object (&provider);
+}
+
+static gboolean
+move_next_alternate (GtkWidget *widget,
+ GVariant *param,
+ gpointer user_data)
+{
+ GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)widget;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+
+ if (self->alternates == NULL || self->alternates->len == 0)
+ {
+ return FALSE;
+ }
+
+ if ((guint)(self->alternate + 1) < self->alternates->len)
+ {
+ self->alternate++;
+ }
+ else
+ {
+ self->alternate = -1;
+ }
+
+ gtk_source_completion_list_box_do_update (self, FALSE);
+
+ return TRUE;
+}
+
+static void
+move_next_alternate_action (GtkWidget *widget,
+ const char *action_name,
+ GVariant *param)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (widget));
+
+ move_next_alternate (widget, param, NULL);
+}
+
+static gboolean
+move_previous_alternate (GtkWidget *widget,
+ GVariant *param,
+ gpointer user_data)
+{
+ GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)widget;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+
+ if (self->alternates == NULL || self->alternates->len == 0)
+ {
+ return FALSE;
+ }
+
+ if (self->alternate < 0)
+ {
+ self->alternate = self->alternates->len - 1;
+ }
+ else
+ {
+ self->alternate--;
+ }
+
+ gtk_source_completion_list_box_do_update (self, FALSE);
+
+ return TRUE;
+}
+
+static void
+move_previous_alternate_action (GtkWidget *widget,
+ const char *action_name,
+ GVariant *param)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (widget));
+
+ move_previous_alternate (widget, param, NULL);
+}
+
+static guint
+gtk_source_completion_list_box_get_offset (GtkSourceCompletionListBox *self)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+
+ return gtk_adjustment_get_value (self->vadjustment);
+}
+
+static void
+gtk_source_completion_list_box_set_offset (GtkSourceCompletionListBox *self,
+ guint offset)
+{
+ double value = offset;
+ double page_size;
+ double upper;
+ double lower;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+
+ lower = gtk_adjustment_get_lower (self->vadjustment);
+ upper = gtk_adjustment_get_upper (self->vadjustment);
+ page_size = gtk_adjustment_get_page_size (self->vadjustment);
+
+ if (value > (upper - page_size))
+ {
+ value = upper - page_size;
+ }
+
+ if (value < lower)
+ {
+ value = lower;
+ }
+
+ gtk_adjustment_set_value (self->vadjustment, value);
+}
+
+static void
+gtk_source_completion_list_box_value_changed (GtkSourceCompletionListBox *self,
+ GtkAdjustment *vadj)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+ g_assert (GTK_IS_ADJUSTMENT (vadj));
+
+ gtk_source_completion_list_box_queue_update (self);
+}
+
+static void
+gtk_source_completion_list_box_set_hadjustment (GtkSourceCompletionListBox *self,
+ GtkAdjustment *hadjustment)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+ g_assert (!hadjustment || GTK_IS_ADJUSTMENT (hadjustment));
+
+ if (g_set_object (&self->hadjustment, hadjustment))
+ {
+ gtk_source_completion_list_box_queue_update (self);
+ }
+}
+
+static void
+gtk_source_completion_list_box_set_vadjustment (GtkSourceCompletionListBox *self,
+ GtkAdjustment *vadjustment)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+ g_assert (!vadjustment || GTK_IS_ADJUSTMENT (vadjustment));
+
+ if (self->vadjustment == vadjustment)
+ return;
+
+ if (self->vadjustment)
+ {
+ g_signal_handlers_disconnect_by_func (self->vadjustment,
+ G_CALLBACK
(gtk_source_completion_list_box_value_changed),
+ self);
+ g_clear_object (&self->vadjustment);
+ }
+
+ if (vadjustment)
+ {
+ self->vadjustment = g_object_ref (vadjustment);
+
+ gtk_adjustment_set_lower (self->vadjustment, 0);
+ gtk_adjustment_set_upper (self->vadjustment, 0);
+ gtk_adjustment_set_value (self->vadjustment, 0);
+ gtk_adjustment_set_step_increment (self->vadjustment, 1);
+ gtk_adjustment_set_page_size (self->vadjustment, self->n_rows);
+ gtk_adjustment_set_page_increment (self->vadjustment, self->n_rows);
+
+ g_signal_connect_object (self->vadjustment,
+ "value-changed",
+ G_CALLBACK (gtk_source_completion_list_box_value_changed),
+ self,
+ G_CONNECT_SWAPPED);
+ }
+
+ gtk_source_completion_list_box_queue_update (self);
+}
+
+static guint
+get_row_at_y (GtkSourceCompletionListBox *self,
+ double y)
+{
+ GtkAllocation alloc;
+ guint offset;
+ guint n_items;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+ g_assert (G_IS_LIST_MODEL (self->context));
+
+ gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+
+ offset = gtk_source_completion_list_box_get_offset (self);
+
+ n_items = g_list_model_get_n_items (G_LIST_MODEL (self->context));
+ n_items = MAX (1, MIN (self->n_rows, n_items));
+
+ return offset + (y / (alloc.height / n_items));
+}
+
+static void
+click_gesture_pressed (GtkGestureClick *gesture,
+ guint n_press,
+ double x,
+ double y,
+ GtkSourceCompletionListBox *self)
+{
+ int selected;
+
+ g_assert (GTK_IS_GESTURE_CLICK (gesture));
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+
+ if (self->context == NULL)
+ {
+ return;
+ }
+
+ selected = get_row_at_y (self, y);
+
+ if (selected != self->selected)
+ {
+ gtk_source_completion_list_box_set_selected (self, selected);
+ }
+ else
+ {
+ GtkSourceCompletionProvider *provider = NULL;
+ GtkSourceCompletionProposal *proposal = NULL;
+
+ if (self->selected >= 0 &&
+ self->selected < (int)g_list_model_get_n_items (G_LIST_MODEL (self->context)) &&
+ _gtk_source_completion_context_get_item_full (self->context, self->selected, &provider,
&proposal))
+ {
+ _gtk_source_completion_activate (gtk_source_completion_context_get_completion
(self->context),
+ self->context, provider, proposal);
+ g_clear_object (&provider);
+ g_clear_object (&proposal);
+ }
+ }
+}
+
+static gboolean
+move_binding_cb (GtkWidget *widget,
+ GVariant *param,
+ gpointer user_data)
+{
+ GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)widget;
+ int direction = 0;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+
+ g_variant_get (param, "(i)", &direction);
+
+ if (ABS (direction) == 1)
+ {
+ _gtk_source_completion_list_box_move_cursor (self, GTK_MOVEMENT_DISPLAY_LINES, direction);
+ }
+ else
+ {
+ _gtk_source_completion_list_box_move_cursor (self, GTK_MOVEMENT_PAGES, direction > 0 ? 1 :
-1);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+activate_nth_cb (GtkWidget *widget,
+ GVariant *param,
+ gpointer user_data)
+{
+ GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)widget;
+ GtkSourceCompletionProvider *provider = NULL;
+ GtkSourceCompletionProposal *proposal = NULL;
+ int nth = 0;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+
+ if (self->context == NULL)
+ {
+ return FALSE;
+ }
+
+ g_variant_get (param, "(i)", &nth);
+
+ if (nth == 0 && self->selected >= 0)
+ {
+ nth = self->selected;
+ }
+ else
+ {
+ nth--;
+ }
+
+ if (nth < 0 || (guint)nth >= g_list_model_get_n_items (G_LIST_MODEL (self->context)))
+ {
+ return FALSE;
+ }
+
+ if (!_gtk_source_completion_context_get_item_full (self->context, nth, &provider, &proposal))
+ {
+ return FALSE;
+ }
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
+ g_assert (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal));
+
+ _gtk_source_completion_activate (gtk_source_completion_context_get_completion (self->context),
+ self->context,
+ provider,
+ proposal);
+
+ g_clear_object (&proposal);
+ g_clear_object (&provider);
+
+ return TRUE;
+}
+
+static gboolean
+_gtk_source_completion_list_box_key_pressed_cb (GtkSourceCompletionListBox *self,
+ guint keyval,
+ guint keycode,
+ GdkModifierType state,
+ GtkEventControllerKey *key)
+{
+ GtkSourceCompletionProvider *provider = NULL;
+ GtkSourceCompletionProposal *proposal = NULL;
+ gboolean ret = FALSE;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+ g_assert (GTK_IS_EVENT_CONTROLLER_KEY (key));
+
+ if (self->context == NULL)
+ {
+ return FALSE;
+ }
+
+ if (_gtk_source_completion_list_box_get_selected (self, &provider, &proposal))
+ {
+ if (gtk_source_completion_provider_key_activates (provider, self->context, proposal, keyval,
state))
+ {
+ _gtk_source_completion_activate (gtk_source_completion_context_get_completion
(self->context),
+ self->context,
+ provider,
+ proposal);
+ ret = TRUE;
+ }
+ }
+
+ g_clear_object (&proposal);
+ g_clear_object (&provider);
+
+ return ret;
+}
+
+static void
+gtk_source_completion_list_box_constructed (GObject *object)
+{
+ GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)object;
+
+ G_OBJECT_CLASS (gtk_source_completion_list_box_parent_class)->constructed (object);
+
+ if (self->hadjustment == NULL)
+ {
+ self->hadjustment = gtk_adjustment_new (0, 0, 0, 0, 0, 0);
+ }
+
+ if (self->vadjustment == NULL)
+ {
+ self->vadjustment = gtk_adjustment_new (0, 0, 0, 0, 0, 0);
+ }
+
+ gtk_adjustment_set_lower (self->hadjustment, 0);
+ gtk_adjustment_set_upper (self->hadjustment, 0);
+ gtk_adjustment_set_value (self->hadjustment, 0);
+
+ gtk_source_completion_list_box_queue_update (self);
+}
+
+static void
+gtk_source_completion_list_box_dispose (GObject *object)
+{
+ GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)object;
+
+ if (self->box != NULL)
+ {
+ gtk_widget_unparent (GTK_WIDGET (self->box));
+ self->box = NULL;
+ }
+
+ g_clear_object (&self->before_size_group);
+ g_clear_object (&self->typed_text_size_group);
+ g_clear_object (&self->after_size_group);
+ g_clear_object (&self->hadjustment);
+ g_clear_object (&self->vadjustment);
+ g_clear_pointer (&self->font_attrs, pango_attr_list_unref);
+
+ G_OBJECT_CLASS (gtk_source_completion_list_box_parent_class)->dispose (object);
+}
+
+static void
+gtk_source_completion_list_box_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkSourceCompletionListBox *self = GTK_SOURCE_COMPLETION_LIST_BOX (object);
+
+ switch (prop_id)
+ {
+ case PROP_N_ALTERNATES:
+ g_value_set_int (value, self->alternates ? self->alternates->len : 0);
+ break;
+
+ case PROP_ALTERNATE:
+ g_value_set_int (value, self->alternate);
+ break;
+
+ case PROP_CONTEXT:
+ g_value_set_object (value, _gtk_source_completion_list_box_get_context (self));
+ break;
+
+ case PROP_PROPOSAL:
+ g_value_take_object (value, _gtk_source_completion_list_box_get_proposal (self));
+ break;
+
+ case PROP_N_ROWS:
+ g_value_set_uint (value, _gtk_source_completion_list_box_get_n_rows (self));
+ break;
+
+ case PROP_HADJUSTMENT:
+ g_value_set_object (value, self->hadjustment);
+ break;
+
+ case PROP_VADJUSTMENT:
+ g_value_set_object (value, self->vadjustment);
+ break;
+
+ case PROP_HSCROLL_POLICY:
+ g_value_set_enum (value, GTK_SCROLL_NATURAL);
+ break;
+
+ case PROP_VSCROLL_POLICY:
+ g_value_set_enum (value, GTK_SCROLL_NATURAL);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtk_source_completion_list_box_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkSourceCompletionListBox *self = GTK_SOURCE_COMPLETION_LIST_BOX (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONTEXT:
+ _gtk_source_completion_list_box_set_context (self, g_value_get_object (value));
+ break;
+
+ case PROP_N_ROWS:
+ _gtk_source_completion_list_box_set_n_rows (self, g_value_get_uint (value));
+ break;
+
+ case PROP_HADJUSTMENT:
+ gtk_source_completion_list_box_set_hadjustment (self, g_value_get_object (value));
+ break;
+
+ case PROP_VADJUSTMENT:
+ gtk_source_completion_list_box_set_vadjustment (self, g_value_get_object (value));
+ break;
+
+ case PROP_HSCROLL_POLICY:
+ /* Do nothing */
+ break;
+
+ case PROP_VSCROLL_POLICY:
+ /* Do nothing */
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtk_source_completion_list_box_class_init (GtkSourceCompletionListBoxClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructed = gtk_source_completion_list_box_constructed;
+ object_class->dispose = gtk_source_completion_list_box_dispose;
+ object_class->get_property = gtk_source_completion_list_box_get_property;
+ object_class->set_property = gtk_source_completion_list_box_set_property;
+
+ properties [PROP_ALTERNATE] =
+ g_param_spec_int ("alternate",
+ "Alternate",
+ "The alternate to choose",
+ -1, G_MAXINT, -1,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_N_ALTERNATES] =
+ g_param_spec_int ("n-alternates",
+ "N Alternates",
+ "The number of alternates",
+ 0, G_MAXINT, 0,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_CONTEXT] =
+ g_param_spec_object ("context",
+ "Context",
+ "The context being displayed",
+ GTK_SOURCE_TYPE_COMPLETION_CONTEXT,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_HADJUSTMENT] =
+ g_param_spec_object ("hadjustment", NULL, NULL,
+ GTK_TYPE_ADJUSTMENT,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_HSCROLL_POLICY] =
+ g_param_spec_enum ("hscroll-policy", NULL, NULL,
+ GTK_TYPE_SCROLLABLE_POLICY,
+ GTK_SCROLL_NATURAL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_VADJUSTMENT] =
+ g_param_spec_object ("vadjustment", NULL, NULL,
+ GTK_TYPE_ADJUSTMENT,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_VSCROLL_POLICY] =
+ g_param_spec_enum ("vscroll-policy", NULL, NULL,
+ GTK_TYPE_SCROLLABLE_POLICY,
+ GTK_SCROLL_NATURAL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_PROPOSAL] =
+ g_param_spec_object ("proposal",
+ "Proposal",
+ "The proposal that is currently selected",
+ GTK_SOURCE_TYPE_COMPLETION_PROPOSAL,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_N_ROWS] =
+ g_param_spec_uint ("n-rows",
+ "N Rows",
+ "The number of visible rows",
+ 1, 32, 5,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY |
G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ signals [REPOSITION] =
+ g_signal_new_class_handler ("reposition",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ NULL, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ g_signal_set_va_marshaller (signals [REPOSITION],
+ G_TYPE_FROM_CLASS (klass),
+ g_cclosure_marshal_VOID__VOIDv);
+
+ gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+ gtk_widget_class_set_css_name (widget_class, "list");
+
+ gtk_widget_class_install_action (widget_class, "proposal.move-next-alternate", NULL,
move_next_alternate_action);
+ gtk_widget_class_install_action (widget_class, "proposal.move-previous-alternate", NULL,
move_previous_alternate_action);
+
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_Down, 0, move_binding_cb, "(i)", 1);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_Up, 0, move_binding_cb, "(i)", -1);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_Page_Up, 0, move_binding_cb, "(i)", -2);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_Page_Down, 0, move_binding_cb, "(i)", 2);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_1, GDK_ALT_MASK, activate_nth_cb, "(i)", 1);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_2, GDK_ALT_MASK, activate_nth_cb, "(i)", 2);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_3, GDK_ALT_MASK, activate_nth_cb, "(i)", 3);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_4, GDK_ALT_MASK, activate_nth_cb, "(i)", 4);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_5, GDK_ALT_MASK, activate_nth_cb, "(i)", 5);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_6, GDK_ALT_MASK, activate_nth_cb, "(i)", 6);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_7, GDK_ALT_MASK, activate_nth_cb, "(i)", 7);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_8, GDK_ALT_MASK, activate_nth_cb, "(i)", 8);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_9, GDK_ALT_MASK, activate_nth_cb, "(i)", 9);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_Return, 0, activate_nth_cb, "(i)", 0);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_KP_Enter, 0, activate_nth_cb, "(i)", 0);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_Tab, 0, activate_nth_cb, "(i)", 0);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_Right, 0, move_next_alternate, NULL);
+ gtk_widget_class_add_binding (widget_class, GDK_KEY_Left, 0, move_previous_alternate, NULL);
+ gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Escape, 0, "assistant.hide", NULL);
+
+ g_type_ensure (GTK_SOURCE_TYPE_COMPLETION_LIST_BOX_ROW);
+}
+
+static void
+gtk_source_completion_list_box_init (GtkSourceCompletionListBox *self)
+{
+ GtkEventController *key;
+
+ key = gtk_event_controller_key_new ();
+ g_signal_connect_swapped (key,
+ "key-pressed",
+ G_CALLBACK (_gtk_source_completion_list_box_key_pressed_cb),
+ self);
+ gtk_widget_add_controller (GTK_WIDGET (self), key);
+
+ self->box = g_object_new (GTK_TYPE_BOX,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ "visible", TRUE,
+ NULL);
+ gtk_widget_set_parent (GTK_WIDGET (self->box), GTK_WIDGET (self));
+
+ self->selected = -1;
+ self->alternate = -1;
+ self->before_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ self->typed_text_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ self->after_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ self->click_gesture = gtk_gesture_click_new ();
+ gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (self->click_gesture),
GTK_PHASE_BUBBLE);
+ gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (self->click_gesture), FALSE);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (self->click_gesture), GDK_BUTTON_PRIMARY);
+ g_signal_connect_object (self->click_gesture, "pressed",
+ G_CALLBACK (click_gesture_pressed), self, 0);
+ gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (self->click_gesture));
+}
+
+static void
+gtk_source_completion_list_box_do_update (GtkSourceCompletionListBox *self,
+ gboolean update_selection)
+{
+ UpdateState state = {0};
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+
+ state.self = self;
+ state.context = self->context;
+ state.position = gtk_source_completion_list_box_get_offset (self);
+ state.selected = self->selected;
+
+ if (self->context != NULL)
+ {
+ state.n_items = g_list_model_get_n_items (G_LIST_MODEL (self->context));
+ }
+
+ state.position = MIN (state.position, MAX (state.n_items, self->n_rows) - self->n_rows);
+ state.selected = MIN (self->selected, state.n_items ? (int)state.n_items - 1 : 0);
+
+ if (gtk_adjustment_get_upper (self->vadjustment) != state.n_items)
+ {
+ gtk_adjustment_set_upper (self->vadjustment, state.n_items);
+ }
+
+ for (GtkWidget *iter = gtk_widget_get_first_child (GTK_WIDGET (self->box));
+ iter != NULL;
+ iter = gtk_widget_get_next_sibling (iter))
+ {
+ GtkSourceCompletionProposal *proposal = NULL;
+ GtkSourceCompletionProvider *provider = NULL;
+ gboolean has_alternates = FALSE;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX_ROW (iter));
+
+ if (state.selected >= 0 && state.position == (guint)state.selected)
+ {
+ gtk_widget_set_state_flags (iter, GTK_STATE_FLAG_SELECTED, FALSE);
+ }
+ else
+ {
+ gtk_widget_unset_state_flags (iter, GTK_STATE_FLAG_SELECTED);
+ }
+
+ if (state.context != NULL && state.position < state.n_items)
+ {
+ _gtk_source_completion_context_get_item_full (state.context,
+ state.position,
+ &provider,
+ &proposal);
+
+ if (state.selected == (int)state.position)
+ {
+ if (self->alternate >= 0 && self->alternate < (int)self->alternates->len)
+ {
+ g_clear_object (&proposal);
+ proposal = g_object_ref (g_ptr_array_index (self->alternates,
self->alternate));
+ }
+
+ has_alternates = self->alternates != NULL && self->alternates->len > 0;
+ }
+
+ _gtk_source_completion_list_box_row_display (GTK_SOURCE_COMPLETION_LIST_BOX_ROW
(iter),
+ state.context,
+ provider,
+ proposal,
+ has_alternates);
+
+ if (gtk_widget_get_visible (iter))
+ {
+ gtk_widget_queue_resize (iter);
+ }
+ else
+ {
+ gtk_widget_show (iter);
+ }
+ }
+ else
+ {
+ _gtk_source_completion_list_box_row_display (GTK_SOURCE_COMPLETION_LIST_BOX_ROW
(iter),
+ NULL, NULL, NULL, FALSE);
+ gtk_widget_hide (GTK_WIDGET (iter));
+ }
+
+ state.position++;
+
+ g_clear_object (&proposal);
+ g_clear_object (&provider);
+ }
+
+ if (update_selection)
+ {
+ gtk_source_completion_list_box_set_selected (self, state.selected);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROPOSAL]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_N_ALTERNATES]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ALTERNATE]);
+
+ g_signal_emit (self, signals [REPOSITION], 0);
+}
+
+static gboolean
+gtk_source_completion_list_box_update_cb (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ gpointer user_data)
+{
+ GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)widget;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+
+ gtk_source_completion_list_box_do_update (self, TRUE);
+ self->queued_update = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static void
+gtk_source_completion_list_box_queue_update (GtkSourceCompletionListBox *self)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+
+ /* See gtk_source_completion_list_box_set_selected() for while this needs
+ * to be set here to avoid re-entrancy.
+ */
+ if (self->queued_update == 0)
+ {
+ self->queued_update = gtk_widget_add_tick_callback (GTK_WIDGET (self),
+ gtk_source_completion_list_box_update_cb,
+ NULL, NULL);
+ }
+}
+
+GtkWidget *
+_gtk_source_completion_list_box_new (void)
+{
+ return g_object_new (GTK_SOURCE_TYPE_COMPLETION_LIST_BOX, NULL);
+}
+
+guint
+_gtk_source_completion_list_box_get_n_rows (GtkSourceCompletionListBox *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self), 0);
+
+ return self->n_rows;
+}
+
+void
+_gtk_source_completion_list_box_set_n_rows (GtkSourceCompletionListBox *self,
+ guint n_rows)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+ g_return_if_fail (n_rows > 0);
+ g_return_if_fail (n_rows <= 32);
+
+ if (n_rows != self->n_rows)
+ {
+ GtkWidget *tmp;
+
+ while ((tmp = gtk_widget_get_first_child (GTK_WIDGET (self->box))))
+ {
+ gtk_box_remove (self->box, tmp);
+ }
+
+ self->n_rows = n_rows;
+
+ if (self->vadjustment != NULL)
+ {
+ gtk_adjustment_set_page_size (self->vadjustment, n_rows);
+ }
+
+ for (guint i = 0; i < n_rows; i++)
+ {
+ GtkWidget *row;
+
+ row = _gtk_source_completion_list_box_row_new ();
+ gtk_widget_set_can_focus (GTK_WIDGET (row), FALSE);
+ _gtk_source_completion_list_box_row_attach (GTK_SOURCE_COMPLETION_LIST_BOX_ROW (row),
+ self->before_size_group,
+ self->typed_text_size_group,
+ self->after_size_group);
+ _gtk_source_completion_list_box_row_set_attrs (GTK_SOURCE_COMPLETION_LIST_BOX_ROW
(row),
+ self->font_attrs);
+ gtk_box_append (self->box, row);
+ }
+
+ gtk_source_completion_list_box_queue_update (self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_N_ROWS]);
+ }
+}
+
+/**
+ * gtk_source_completion_list_box_get_proposal:
+ * @self: a #GtkSourceCompletionListBox
+ *
+ * Gets the currently selected proposal, or %NULL if no proposal is selected
+ *
+ * Returns: (nullable) (transfer full): a #GtkSourceCompletionProposal or %NULL
+ *
+ * Since: 5.0
+ */
+GtkSourceCompletionProposal *
+_gtk_source_completion_list_box_get_proposal (GtkSourceCompletionListBox *self)
+{
+ GtkSourceCompletionProposal *ret = NULL;
+
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self), NULL);
+
+ if (self->context != NULL &&
+ self->selected < (int)g_list_model_get_n_items (G_LIST_MODEL (self->context)))
+ ret = g_list_model_get_item (G_LIST_MODEL (self->context), self->selected);
+
+ g_return_val_if_fail (!ret || GTK_SOURCE_IS_COMPLETION_PROPOSAL (ret), NULL);
+
+ return ret;
+}
+
+/**
+ * _gtk_source_completion_list_box_get_selected:
+ * @self: an #GtkSourceCompletionListBox
+ * @provider: (out) (transfer full) (optional): a location for the provider
+ * @proposal: (out) (transfer full) (optional): a location for the proposal
+ *
+ * Gets the selected item if there is any.
+ *
+ * If there is a selection, %TRUE is returned and @provider and @proposal
+ * are set to the selected provider/proposal.
+ *
+ * Returns: %TRUE if there is a selection
+ *
+ * Since: 5.0
+ */
+gboolean
+_gtk_source_completion_list_box_get_selected (GtkSourceCompletionListBox *self,
+ GtkSourceCompletionProvider **provider,
+ GtkSourceCompletionProposal **proposal)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self), FALSE);
+
+ if (self->context != NULL)
+ {
+ guint n_items = g_list_model_get_n_items (G_LIST_MODEL (self->context));
+
+ if (n_items > 0)
+ {
+ if (self->selected >= 0)
+ {
+ gint selected = MIN (self->selected, (int)n_items - 1);
+ _gtk_source_completion_context_get_item_full (self->context, selected,
provider, proposal);
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * gtk_source_completion_list_box_get_context:
+ * @self: a #GtkSourceCompletionListBox
+ *
+ * Gets the context that is being displayed in the list box.
+ *
+ * Returns: (transfer none) (nullable): an #GtkSourceCompletionContext or %NULL
+ *
+ * Since: 5.0
+ */
+GtkSourceCompletionContext *
+_gtk_source_completion_list_box_get_context (GtkSourceCompletionListBox *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self), NULL);
+
+ return self->context;
+}
+
+static void
+gtk_source_completion_list_box_items_changed_cb (GtkSourceCompletionListBox *self,
+ guint position,
+ guint removed,
+ guint added,
+ GListModel *model)
+{
+ guint offset;
+
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+ g_assert (G_IS_LIST_MODEL (model));
+
+ offset = gtk_source_completion_list_box_get_offset (self);
+
+ /* Skip widget resize if results are after visible area */
+ if (position >= offset + self->n_rows)
+ return;
+
+ gtk_source_completion_list_box_queue_update (self);
+}
+
+/**
+ * gtk_source_completion_list_box_set_context:
+ * @self: a #GtkSourceCompletionListBox
+ * @context: the #GtkSourceCompletionContext
+ *
+ * Sets the context to be displayed.
+ *
+ * Since: 5.0
+ */
+void
+_gtk_source_completion_list_box_set_context (GtkSourceCompletionListBox *self,
+ GtkSourceCompletionContext *context)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+ g_return_if_fail (!context || GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
+
+ if (self->context == context)
+ return;
+
+ if (self->context != NULL && self->items_changed_handler != 0)
+ {
+ g_signal_handler_disconnect (self->context, self->items_changed_handler);
+ self->items_changed_handler = 0;
+ }
+
+ g_set_object (&self->context, context);
+
+ if (self->context != NULL)
+ {
+ self->items_changed_handler =
+ g_signal_connect_object (self->context,
+ "items-changed",
+ G_CALLBACK (gtk_source_completion_list_box_items_changed_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ }
+
+ gtk_source_completion_list_box_set_selected (self, -1);
+ gtk_adjustment_set_value (self->vadjustment, 0);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONTEXT]);
+}
+
+GtkSourceCompletionListBoxRow *
+_gtk_source_completion_list_box_get_first (GtkSourceCompletionListBox *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self), NULL);
+
+ return GTK_SOURCE_COMPLETION_LIST_BOX_ROW (gtk_widget_get_first_child (GTK_WIDGET (self->box)));
+}
+
+void
+_gtk_source_completion_list_box_move_cursor (GtkSourceCompletionListBox *self,
+ GtkMovementStep step,
+ int direction)
+{
+ int n_items;
+ int offset;
+
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+
+ if (self->context == NULL || direction == 0)
+ return;
+
+ if (!(n_items = g_list_model_get_n_items (G_LIST_MODEL (self->context))))
+ return;
+
+ /* n_items is signed so that we can do negative comparison */
+ if (n_items < 0)
+ return;
+
+ if (step == GTK_MOVEMENT_BUFFER_ENDS)
+ {
+ if (direction > 0)
+ {
+ gtk_source_completion_list_box_set_offset (self, n_items);
+ gtk_source_completion_list_box_set_selected (self, n_items - 1);
+ }
+ else
+ {
+ gtk_source_completion_list_box_set_offset (self, 0);
+ gtk_source_completion_list_box_set_selected (self, -1);
+ }
+
+ gtk_source_completion_list_box_queue_update (self);
+
+ return;
+ }
+
+ if ((direction < 0 && self->selected == 0) ||
+ (direction > 0 && self->selected == n_items - 1))
+ {
+ return;
+ }
+
+ if (step == GTK_MOVEMENT_PAGES)
+ {
+ direction *= self->n_rows;
+ }
+
+ if ((self->selected + direction) > n_items)
+ {
+ gtk_source_completion_list_box_set_selected (self, n_items - 1);
+ }
+ else if ((self->selected + direction) < 0)
+ {
+ gtk_source_completion_list_box_set_selected (self, 0);
+ }
+ else
+ {
+ gtk_source_completion_list_box_set_selected (self, self->selected + direction);
+ }
+
+ offset = gtk_source_completion_list_box_get_offset (self);
+
+ if (self->selected < offset)
+ {
+ gtk_source_completion_list_box_set_offset (self, self->selected);
+ }
+ else if (self->selected >= (int)(offset + self->n_rows))
+ {
+ gtk_source_completion_list_box_set_offset (self, self->selected - self->n_rows + 1);
+ }
+
+ gtk_source_completion_list_box_queue_update (self);
+}
+
+void
+_gtk_source_completion_list_box_set_font_desc (GtkSourceCompletionListBox *self,
+ const PangoFontDescription *font_desc)
+{
+ GtkWidget *iter;
+
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self));
+
+ g_clear_pointer (&self->font_attrs, pango_attr_list_unref);
+
+ if (font_desc)
+ {
+ self->font_attrs = pango_attr_list_new ();
+
+ if (font_desc != NULL)
+ {
+ pango_attr_list_insert (self->font_attrs, pango_attr_font_desc_new (font_desc));
+ pango_attr_list_insert (self->font_attrs, pango_attr_font_features_new ("tnum"));
+ }
+ }
+
+ for ((iter = gtk_widget_get_first_child (GTK_WIDGET (self->box)));
+ iter != NULL;
+ iter = gtk_widget_get_next_sibling (iter))
+ {
+ _gtk_source_completion_list_box_row_set_attrs (GTK_SOURCE_COMPLETION_LIST_BOX_ROW (iter),
+ self->font_attrs);
+ }
+}
+
+int
+_gtk_source_completion_list_box_get_alternate (GtkSourceCompletionListBox *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self), 0);
+
+ return self->alternate + 1;
+}
+
+guint
+_gtk_source_completion_list_box_get_n_alternates (GtkSourceCompletionListBox *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self), 0);
+
+ return self->alternates ? self->alternates->len : 0;
+}
diff --git a/gtksourceview/gtksourcecompletionlistboxrow-private.h
b/gtksourceview/gtksourcecompletionlistboxrow-private.h
new file mode 100644
index 00000000..7e2edda5
--- /dev/null
+++ b/gtksourceview/gtksourcecompletionlistboxrow-private.h
@@ -0,0 +1,49 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 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 <gtk/gtk.h>
+
+#include "gtksourcetypes.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_COMPLETION_LIST_BOX_ROW (gtk_source_completion_list_box_row_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkSourceCompletionListBoxRow, gtk_source_completion_list_box_row, GTK_SOURCE,
COMPLETION_LIST_BOX_ROW, GtkListBoxRow)
+
+GtkWidget *_gtk_source_completion_list_box_row_new (void);
+void _gtk_source_completion_list_box_row_display (GtkSourceCompletionListBoxRow
*self,
+ GtkSourceCompletionContext
*context,
+ GtkSourceCompletionProvider
*provider,
+ GtkSourceCompletionProposal
*proposal,
+ gboolean
has_alternates);
+gint _gtk_source_completion_list_box_row_get_x_offset (GtkSourceCompletionListBoxRow
*self,
+ GtkWidget
*toplevel);
+void _gtk_source_completion_list_box_row_set_attrs (GtkSourceCompletionListBoxRow
*self,
+ PangoAttrList
*attrs);
+void _gtk_source_completion_list_box_row_attach (GtkSourceCompletionListBoxRow
*self,
+ GtkSizeGroup
*before,
+ GtkSizeGroup
*typed_text,
+ GtkSizeGroup
*after);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcecompletionlistboxrow.c b/gtksourceview/gtksourcecompletionlistboxrow.c
new file mode 100644
index 00000000..3d3a81c2
--- /dev/null
+++ b/gtksourceview/gtksourcecompletionlistboxrow.c
@@ -0,0 +1,212 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 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 "gtksourcecompletioncell-private.h"
+#include "gtksourcecompletioncontext-private.h"
+#include "gtksourcecompletionlistboxrow-private.h"
+#include "gtksourcecompletionproposal.h"
+#include "gtksourcecompletionprovider.h"
+
+struct _GtkSourceCompletionListBoxRow
+{
+ GtkListBoxRow parent_instance;
+
+ GtkSourceCompletionProposal *proposal;
+ PangoAttrList *attrs;
+
+ GtkBox *box;
+ GtkBox *more;
+ GtkSourceCompletionCell *icon;
+ GtkSourceCompletionCell *before;
+ GtkSourceCompletionCell *typed_text;
+ GtkSourceCompletionCell *after;
+};
+
+enum {
+ PROP_0,
+ PROP_PROPOSAL,
+ N_PROPS
+};
+
+G_DEFINE_TYPE (GtkSourceCompletionListBoxRow, gtk_source_completion_list_box_row, GTK_TYPE_LIST_BOX_ROW)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gtk_source_completion_list_box_row_finalize (GObject *object)
+{
+ GtkSourceCompletionListBoxRow *self = (GtkSourceCompletionListBoxRow *)object;
+
+ g_clear_pointer (&self->attrs, pango_attr_list_unref);
+
+ G_OBJECT_CLASS (gtk_source_completion_list_box_row_parent_class)->finalize (object);
+}
+
+static void
+gtk_source_completion_list_box_row_class_init (GtkSourceCompletionListBoxRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = gtk_source_completion_list_box_row_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/gtksourceview/gtksourcecompletionlistboxrow.ui");
+ gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionListBoxRow, box);
+ gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionListBoxRow, icon);
+ gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionListBoxRow, before);
+ gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionListBoxRow, typed_text);
+ gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionListBoxRow, after);
+ gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionListBoxRow, more);
+
+ g_type_ensure (GTK_SOURCE_TYPE_COMPLETION_CELL);
+}
+
+static void
+gtk_source_completion_list_box_row_init (GtkSourceCompletionListBoxRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+GtkWidget *
+_gtk_source_completion_list_box_row_new (void)
+{
+ return g_object_new (GTK_SOURCE_TYPE_COMPLETION_LIST_BOX_ROW, NULL);
+}
+
+GtkSourceCompletionProposal *
+_gtk_source_completion_list_box_row_get_proposal (GtkSourceCompletionListBoxRow *self)
+{
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX_ROW (self), NULL);
+
+ return self->proposal;
+}
+
+void
+_gtk_source_completion_list_box_row_display (GtkSourceCompletionListBoxRow *self,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProvider *provider,
+ GtkSourceCompletionProposal *proposal,
+ gboolean has_alternates)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX_ROW (self));
+ g_return_if_fail (!context || GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
+ g_return_if_fail (!provider || GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
+ g_return_if_fail (!proposal || GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal));
+
+ if (proposal == NULL)
+ {
+ gtk_source_completion_cell_set_widget (self->icon, NULL);
+ gtk_source_completion_cell_set_widget (self->before, NULL);
+ gtk_source_completion_cell_set_widget (self->typed_text, NULL);
+ gtk_source_completion_cell_set_widget (self->after, NULL);
+ }
+ else
+ {
+ gtk_source_completion_provider_display (provider, context, proposal, self->icon);
+ gtk_source_completion_provider_display (provider, context, proposal, self->before);
+ gtk_source_completion_provider_display (provider, context, proposal, self->typed_text);
+ gtk_source_completion_provider_display (provider, context, proposal, self->after);
+ }
+
+ gtk_widget_set_visible (GTK_WIDGET (self->more), has_alternates);
+}
+
+void
+_gtk_source_completion_list_box_row_attach (GtkSourceCompletionListBoxRow *self,
+ GtkSizeGroup *before,
+ GtkSizeGroup *typed_text,
+ GtkSizeGroup *after)
+{
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX_ROW (self));
+ g_return_if_fail (GTK_IS_SIZE_GROUP (before));
+ g_return_if_fail (GTK_IS_SIZE_GROUP (typed_text));
+ g_return_if_fail (GTK_IS_SIZE_GROUP (after));
+
+ gtk_size_group_add_widget (before, GTK_WIDGET (self->before));
+ gtk_size_group_add_widget (typed_text, GTK_WIDGET (self->typed_text));
+ gtk_size_group_add_widget (after, GTK_WIDGET (self->after));
+}
+
+static void
+get_margin_and_border (GtkWidget *widget,
+ GtkBorder *margin,
+ GtkBorder *border)
+{
+ GtkStyleContext *style_context = gtk_widget_get_style_context (widget);
+ gtk_style_context_get_margin (style_context, margin);
+ gtk_style_context_get_border (style_context, border);
+}
+
+gint
+_gtk_source_completion_list_box_row_get_x_offset (GtkSourceCompletionListBoxRow *self,
+ GtkWidget *toplevel)
+{
+ GtkStyleContext *style_context;
+ GtkRequisition min;
+ GtkRequisition nat;
+ GtkBorder margin;
+ GtkBorder border;
+ double x = 0;
+
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX_ROW (self), 0);
+ g_return_val_if_fail (GTK_IS_WIDGET (toplevel), 0);
+
+ for (GtkWidget *iter = GTK_WIDGET (self->box);
+ iter != NULL;
+ iter = gtk_widget_get_parent (iter))
+ {
+ get_margin_and_border (iter, &margin, &border);
+ x += margin.left + border.left;
+
+ if (iter == toplevel)
+ {
+ break;
+ }
+ }
+
+ get_margin_and_border (GTK_WIDGET (self->icon), &margin, &border);
+ gtk_widget_get_preferred_size (GTK_WIDGET (self->icon), &min, &nat);
+ x += margin.left + border.left + nat.width + margin.right + border.right;
+
+ get_margin_and_border (GTK_WIDGET (self->before), &margin, &border);
+ gtk_widget_get_preferred_size (GTK_WIDGET (self->before), &min, &nat);
+ x += margin.left + border.left + nat.width + border.right + margin.right;
+
+ get_margin_and_border (GTK_WIDGET (self->typed_text), &margin, &border);
+ gtk_widget_get_preferred_size (GTK_WIDGET (self->typed_text), &min, &nat);
+ x += margin.left + border.left;
+
+ return -x;
+}
+
+void
+_gtk_source_completion_list_box_row_set_attrs (GtkSourceCompletionListBoxRow *self,
+ PangoAttrList *attrs)
+{
+ g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX_ROW (self));
+
+ _gtk_source_completion_cell_set_attrs (self->icon, attrs);
+ _gtk_source_completion_cell_set_attrs (self->before, attrs);
+ _gtk_source_completion_cell_set_attrs (self->typed_text, attrs);
+ _gtk_source_completion_cell_set_attrs (self->after, attrs);
+}
diff --git a/gtksourceview/gtksourcecompletionlistboxrow.ui b/gtksourceview/gtksourcecompletionlistboxrow.ui
new file mode 100644
index 00000000..671fce2f
--- /dev/null
+++ b/gtksourceview/gtksourcecompletionlistboxrow.ui
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="GtkSourceCompletionListBoxRow" parent="GtkListBoxRow">
+ <property name="can-focus">false</property>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="can-focus">false</property>
+ <property name="hexpand">true</property>
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkSourceCompletionCell" id="icon">
+ <property name="can-focus">false</property>
+ <property name="column">icon</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSourceCompletionCell" id="before">
+ <property name="can-focus">false</property>
+ <property name="column">before</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSourceCompletionCell" id="typed_text">
+ <property name="can-focus">false</property>
+ <property name="column">typed-text</property>
+ <property name="hexpand">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSourceCompletionCell" id="after">
+ <property name="can-focus">false</property>
+ <property name="column">after</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="more">
+ <style>
+ <class name="more"/>
+ </style>
+ <child>
+ <object class="GtkButton" id="more1">
+ <property name="action-name">proposal.move-previous-alternate</property>
+ <property name="can-focus">false</property>
+ <property name="icon-name">pan-start-symbolic</property>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="more2">
+ <property name="action-name">proposal.move-next-alternate</property>
+ <property name="can-focus">false</property>
+ <property name="icon-name">pan-end-symbolic</property>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/gtksourceview/gtksourcecompletionproposal.c b/gtksourceview/gtksourcecompletionproposal.c
index 1fb5cc36..b66e2f83 100644
--- a/gtksourceview/gtksourcecompletionproposal.c
+++ b/gtksourceview/gtksourcecompletionproposal.c
@@ -1,8 +1,7 @@
/*
* This file is part of GtkSourceView
*
- * Copyright 2007 - 2009 Jesús Barbero Rodríguez <chuchiperriman gmail com>
- * Copyright 2009 - Jesse van den Kieboom <jessevdk gnome org>
+ * Copyright 2020 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
@@ -16,319 +15,19 @@
*
* 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/>.
- */
-
-#include "config.h"
-
-#include "gtksourcecompletionproposal.h"
-
-/**
- * SECTION:completionproposal
- * @title: GtkSourceCompletionProposal
- * @short_description: Completion proposal interface
- *
- * The proposal interface represents a completion item in the completion window.
- * It provides information on how to display the completion item and what action
- * should be taken when the completion item is activated.
*
- * The proposal is displayed in the completion window with a label and
- * optionally an icon.
- * The label may be specified using plain text or markup by implementing
- * the corresponding get function. Only one of those get functions
- * should return a value different from %NULL.
- * The icon may be specified as a #GdkTexture, as an icon name or as a #GIcon by
- * implementing the corresponding get function. At most one of those get functions
- * should return a value different from %NULL, if they all return %NULL no icon
- * will be used.
+ * SPDX-License-Identifier: LGPL-2.1-or-later
*/
-enum
-{
- CHANGED,
- N_SIGNALS
-};
-
-static guint signals[N_SIGNALS];
-
-G_DEFINE_INTERFACE (GtkSourceCompletionProposal, gtk_source_completion_proposal, G_TYPE_OBJECT)
-
-static gchar *
-gtk_source_completion_proposal_get_label_default (GtkSourceCompletionProposal *proposal)
-{
- return NULL;
-}
-
-static gchar *
-gtk_source_completion_proposal_get_markup_default (GtkSourceCompletionProposal *proposal)
-{
- return NULL;
-}
-
-static gchar *
-gtk_source_completion_proposal_get_text_default (GtkSourceCompletionProposal *proposal)
-{
- return NULL;
-}
-
-static GdkTexture *
-gtk_source_completion_proposal_get_icon_default (GtkSourceCompletionProposal *proposal)
-{
- return NULL;
-}
-static const gchar *
-gtk_source_completion_proposal_get_icon_name_default (GtkSourceCompletionProposal *proposal)
-{
- return NULL;
-}
-
-static GIcon *
-gtk_source_completion_proposal_get_gicon_default (GtkSourceCompletionProposal *proposal)
-{
- return NULL;
-}
-static gchar *
-gtk_source_completion_proposal_get_info_default (GtkSourceCompletionProposal *proposal)
-{
- return NULL;
-}
+#include "config.h"
-static guint
-gtk_source_completion_proposal_hash_default (GtkSourceCompletionProposal *proposal)
-{
- return g_direct_hash (proposal);
-}
+#include "gtksourcecompletionproposal.h"
-static gboolean
-gtk_source_completion_proposal_equal_default (GtkSourceCompletionProposal *proposal,
- GtkSourceCompletionProposal *other)
-{
- return g_direct_equal (proposal, other);
-}
+G_DEFINE_INTERFACE (GtkSourceCompletionProposal, gtk_source_completion_proposal, G_TYPE_OBJECT)
static void
gtk_source_completion_proposal_default_init (GtkSourceCompletionProposalInterface *iface)
{
- static gboolean initialized = FALSE;
-
- iface->get_label = gtk_source_completion_proposal_get_label_default;
- iface->get_markup = gtk_source_completion_proposal_get_markup_default;
- iface->get_text = gtk_source_completion_proposal_get_text_default;
- iface->get_icon = gtk_source_completion_proposal_get_icon_default;
- iface->get_icon_name = gtk_source_completion_proposal_get_icon_name_default;
- iface->get_gicon = gtk_source_completion_proposal_get_gicon_default;
- iface->get_info = gtk_source_completion_proposal_get_info_default;
- iface->hash = gtk_source_completion_proposal_hash_default;
- iface->equal = gtk_source_completion_proposal_equal_default;
-
- if (!initialized)
- {
- /**
- * GtkSourceCompletionProposal::changed:
- * @proposal: The #GtkSourceCompletionProposal
- *
- * Emitted when the proposal has changed. The completion popup
- * will react to this by updating the shown information.
- *
- */
- signals[CHANGED] =
- g_signal_new ("changed",
- G_TYPE_FROM_INTERFACE (iface),
- G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
- G_STRUCT_OFFSET (GtkSourceCompletionProposalInterface, changed),
- NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
- g_signal_set_va_marshaller (signals[CHANGED],
- G_TYPE_FROM_INTERFACE (iface),
- g_cclosure_marshal_VOID__VOIDv);
-
- initialized = TRUE;
- }
-}
-
-/**
- * gtk_source_completion_proposal_get_label:
- * @proposal: a #GtkSourceCompletionProposal.
- *
- * Gets the label of @proposal. The label is shown in the list of proposals as
- * plain text. If you need any markup (such as bold or italic text), you have
- * to implement gtk_source_completion_proposal_get_markup(). The returned string
- * must be freed with g_free().
- *
- * Returns: a new string containing the label of @proposal.
- */
-gchar *
-gtk_source_completion_proposal_get_label (GtkSourceCompletionProposal *proposal)
-{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), NULL);
-
- return GTK_SOURCE_COMPLETION_PROPOSAL_GET_IFACE (proposal)->get_label (proposal);
-}
-
-/**
- * gtk_source_completion_proposal_get_markup:
- * @proposal: a #GtkSourceCompletionProposal.
- *
- * Gets the label of @proposal with markup. The label is shown in the list of
- * proposals and may contain markup. This will be used instead of
- * gtk_source_completion_proposal_get_label() if implemented. The returned string
- * must be freed with g_free().
- *
- * Returns: a new string containing the label of @proposal with markup.
- */
-gchar *
-gtk_source_completion_proposal_get_markup (GtkSourceCompletionProposal *proposal)
-{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), NULL);
-
- return GTK_SOURCE_COMPLETION_PROPOSAL_GET_IFACE (proposal)->get_markup (proposal);
-}
-
-/**
- * gtk_source_completion_proposal_get_text:
- * @proposal: a #GtkSourceCompletionProposal.
- *
- * Gets the text of @proposal. The text that is inserted into
- * the text buffer when the proposal is activated by the default activation.
- * You are free to implement a custom activation handler in the provider and
- * not implement this function. For more information, see
- * gtk_source_completion_provider_activate_proposal(). The returned string must
- * be freed with g_free().
- *
- * Returns: a new string containing the text of @proposal.
- */
-gchar *
-gtk_source_completion_proposal_get_text (GtkSourceCompletionProposal *proposal)
-{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), NULL);
-
- return GTK_SOURCE_COMPLETION_PROPOSAL_GET_IFACE (proposal)->get_text (proposal);
-}
-
-/**
- * gtk_source_completion_proposal_get_icon:
- * @proposal: a #GtkSourceCompletionProposal.
- *
- * Gets the #GdkTexture for the icon of @proposal.
- *
- * Returns: (nullable) (transfer none): A #GdkTexture with the icon of @proposal.
- */
-GdkTexture *
-gtk_source_completion_proposal_get_icon (GtkSourceCompletionProposal *proposal)
-{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), NULL);
-
- return GTK_SOURCE_COMPLETION_PROPOSAL_GET_IFACE (proposal)->get_icon (proposal);
-}
-
-/**
- * gtk_source_completion_proposal_get_icon_name:
- * @proposal: a #GtkSourceCompletionProposal.
- *
- * Gets the icon name of @proposal.
- *
- * Returns: (nullable) (transfer none): The icon name of @proposal.
- *
- * Since: 3.18
- */
-const gchar *
-gtk_source_completion_proposal_get_icon_name (GtkSourceCompletionProposal *proposal)
-{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), NULL);
-
- return GTK_SOURCE_COMPLETION_PROPOSAL_GET_IFACE (proposal)->get_icon_name (proposal);
-}
-
-/**
- * gtk_source_completion_proposal_get_gicon:
- * @proposal: a #GtkSourceCompletionProposal.
- *
- * Gets the #GIcon for the icon of @proposal.
- *
- * Returns: (nullable) (transfer none): A #GIcon with the icon of @proposal.
- *
- * Since: 3.18
- */
-GIcon *
-gtk_source_completion_proposal_get_gicon (GtkSourceCompletionProposal *proposal)
-{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), NULL);
-
- return GTK_SOURCE_COMPLETION_PROPOSAL_GET_IFACE (proposal)->get_gicon (proposal);
-}
-
-/**
- * gtk_source_completion_proposal_get_info:
- * @proposal: a #GtkSourceCompletionProposal.
- *
- * Gets extra information associated to the proposal. This information will be
- * used to present the user with extra, detailed information about the
- * selected proposal. The returned string must be freed with g_free().
- *
- * Returns: (nullable) (transfer full): a newly-allocated string containing
- * extra information of @proposal or %NULL if no extra information is associated
- * to @proposal.
- */
-gchar *
-gtk_source_completion_proposal_get_info (GtkSourceCompletionProposal *proposal)
-{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), NULL);
-
- return GTK_SOURCE_COMPLETION_PROPOSAL_GET_IFACE (proposal)->get_info (proposal);
-}
-
-/**
- * gtk_source_completion_proposal_hash:
- * @proposal: a #GtkSourceCompletionProposal.
- *
- * Get the hash value of @proposal. This is used to (together with
- * gtk_source_completion_proposal_equal()) to match proposals in the completion
- * model. By default, it uses a direct hash (g_direct_hash()).
- *
- * Returns: The hash value of @proposal.
- */
-guint
-gtk_source_completion_proposal_hash (GtkSourceCompletionProposal *proposal)
-{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), 0);
-
- return GTK_SOURCE_COMPLETION_PROPOSAL_GET_IFACE (proposal)->hash (proposal);
-}
-
-/**
- * gtk_source_completion_proposal_equal:
- * @proposal: a #GtkSourceCompletionProposal.
- * @other: a #GtkSourceCompletionProposal.
- *
- * Get whether two proposal objects are the same. This is used to (together
- * with gtk_source_completion_proposal_hash()) to match proposals in the
- * completion model. By default, it uses direct equality (g_direct_equal()).
- *
- * Returns: %TRUE if @proposal and @object are the same proposal
- */
-gboolean
-gtk_source_completion_proposal_equal (GtkSourceCompletionProposal *proposal,
- GtkSourceCompletionProposal *other)
-{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), FALSE);
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (other), FALSE);
-
- return GTK_SOURCE_COMPLETION_PROPOSAL_GET_IFACE (proposal)->equal (proposal, other);
-}
-
-/**
- * gtk_source_completion_proposal_changed:
- * @proposal: a #GtkSourceCompletionProposal.
- *
- * Emits the "changed" signal on @proposal. This should be called by
- * implementations whenever the name, icon or info of the proposal has
- * changed.
- */
-void
-gtk_source_completion_proposal_changed (GtkSourceCompletionProposal *proposal)
-{
- g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal));
- g_signal_emit (proposal, signals[CHANGED], 0);
}
diff --git a/gtksourceview/gtksourcecompletionproposal.h b/gtksourceview/gtksourcecompletionproposal.h
index 969662ea..723f8012 100644
--- a/gtksourceview/gtksourcecompletionproposal.h
+++ b/gtksourceview/gtksourcecompletionproposal.h
@@ -1,8 +1,7 @@
/*
* This file is part of GtkSourceView
*
- * Copyright 2007 - 2009 Jesús Barbero Rodríguez <chuchiperriman gmail com>
- * Copyright 2009 - Jesse van den Kieboom <jessevdk gnome org>
+ * Copyright 2020 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
@@ -16,90 +15,26 @@
*
* 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 <glib-object.h>
#include "gtksourcetypes.h"
G_BEGIN_DECLS
-#define GTK_SOURCE_TYPE_COMPLETION_PROPOSAL (gtk_source_completion_proposal_get_type ())
+#define GTK_SOURCE_TYPE_COMPLETION_PROPOSAL (gtk_source_completion_proposal_get_type())
-GTK_SOURCE_AVAILABLE_IN_ALL
+GTK_SOURCE_AVAILABLE_IN_5_0
G_DECLARE_INTERFACE (GtkSourceCompletionProposal, gtk_source_completion_proposal, GTK_SOURCE,
COMPLETION_PROPOSAL, GObject)
-/**
- * GtkSourceCompletionProposalInterface:
- * @parent: The parent interface.
- * @get_label: The virtual function pointer for gtk_source_completion_proposal_get_label().
- * By default, %NULL is returned.
- * @get_markup: The virtual function pointer for gtk_source_completion_proposal_get_markup().
- * By default, %NULL is returned.
- * @get_text: The virtual function pointer for gtk_source_completion_proposal_get_text().
- * By default, %NULL is returned.
- * @get_icon: The virtual function pointer for gtk_source_completion_proposal_get_icon().
- * By default, %NULL is returned.
- * @get_icon_name: The virtual function pointer for gtk_source_completion_proposal_get_icon_name().
- * By default, %NULL is returned.
- * @get_gicon: The virtual function pointer for gtk_source_completion_proposal_get_gicon().
- * By default, %NULL is returned.
- * @get_info: The virtual function pointer for gtk_source_completion_proposal_get_info().
- * By default, %NULL is returned.
- * @hash: The virtual function pointer for gtk_source_completion_proposal_hash().
- * By default, it uses a direct hash (g_direct_hash()).
- * @equal: The virtual function pointer for gtk_source_completion_proposal_equal().
- * By default, it uses direct equality (g_direct_equal()).
- * @changed: The function pointer for the #GtkSourceCompletionProposal::changed signal.
- *
- * The virtual function table for #GtkSourceCompletionProposal.
- */
struct _GtkSourceCompletionProposalInterface
{
- GTypeInterface parent;
-
- /* Interface functions */
- gchar *(*get_label) (GtkSourceCompletionProposal *proposal);
- gchar *(*get_markup) (GtkSourceCompletionProposal *proposal);
- gchar *(*get_text) (GtkSourceCompletionProposal *proposal);
- GdkTexture *(*get_icon) (GtkSourceCompletionProposal *proposal);
- const gchar *(*get_icon_name) (GtkSourceCompletionProposal *proposal);
- GIcon *(*get_gicon) (GtkSourceCompletionProposal *proposal);
- gchar *(*get_info) (GtkSourceCompletionProposal *proposal);
- guint (*hash) (GtkSourceCompletionProposal *proposal);
- gboolean (*equal) (GtkSourceCompletionProposal *proposal,
- GtkSourceCompletionProposal *other);
-
- /* Signals */
- void (*changed) (GtkSourceCompletionProposal *proposal);
+ GTypeInterface parent_iface;
};
-GTK_SOURCE_AVAILABLE_IN_ALL
-gchar *gtk_source_completion_proposal_get_label (GtkSourceCompletionProposal *proposal);
-GTK_SOURCE_AVAILABLE_IN_ALL
-gchar *gtk_source_completion_proposal_get_markup (GtkSourceCompletionProposal *proposal);
-GTK_SOURCE_AVAILABLE_IN_ALL
-gchar *gtk_source_completion_proposal_get_text (GtkSourceCompletionProposal *proposal);
-GTK_SOURCE_AVAILABLE_IN_ALL
-GdkTexture *gtk_source_completion_proposal_get_icon (GtkSourceCompletionProposal *proposal);
-GTK_SOURCE_AVAILABLE_IN_3_18
-const gchar *gtk_source_completion_proposal_get_icon_name (GtkSourceCompletionProposal *proposal);
-GTK_SOURCE_AVAILABLE_IN_3_18
-GIcon *gtk_source_completion_proposal_get_gicon (GtkSourceCompletionProposal *proposal);
-GTK_SOURCE_AVAILABLE_IN_ALL
-gchar *gtk_source_completion_proposal_get_info (GtkSourceCompletionProposal *proposal);
-GTK_SOURCE_AVAILABLE_IN_ALL
-void gtk_source_completion_proposal_changed (GtkSourceCompletionProposal *proposal);
-GTK_SOURCE_AVAILABLE_IN_ALL
-guint gtk_source_completion_proposal_hash (GtkSourceCompletionProposal *proposal);
-GTK_SOURCE_AVAILABLE_IN_ALL
-gboolean gtk_source_completion_proposal_equal (GtkSourceCompletionProposal *proposal,
- GtkSourceCompletionProposal *other);
-
G_END_DECLS
diff --git a/gtksourceview/gtksourcecompletionprovider.c b/gtksourceview/gtksourcecompletionprovider.c
index 1eafcc9c..5278bc02 100644
--- a/gtksourceview/gtksourcecompletionprovider.c
+++ b/gtksourceview/gtksourcecompletionprovider.c
@@ -1,8 +1,7 @@
/*
* This file is part of GtkSourceView
*
- * Copyright 2007 - 2009 Jesús Barbero Rodríguez <chuchiperriman gmail com>
- * Copyright 2009 - Jesse van den Kieboom <jessevdk gnome org>
+ * Copyright 2020 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
@@ -16,431 +15,251 @@
*
* 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 "gtksourcecompletionprovider.h"
+#include "gtksourcecompletioncontext.h"
+#include "gtksourcecompletioncell.h"
#include "gtksourcecompletionproposal.h"
-#include "gtksourcecompletioninfo.h"
-
-/**
- * SECTION:completionprovider
- * @title: GtkSourceCompletionProvider
- * @short_description: Completion provider interface
- *
- * You must implement this interface to provide proposals to #GtkSourceCompletion
- *
- * The provider may be displayed in the completion window as a header row, showing
- * its name and optionally an icon.
- * The icon may be specified as a #GdkTexture, as an icon name or as a #GIcon by
- * implementing the corresponding get function. At most one of those get functions
- * should return a value different from %NULL, if they all return %NULL no icon
- * will be used.
- */
+#include "gtksourcecompletionprovider.h"
G_DEFINE_INTERFACE (GtkSourceCompletionProvider, gtk_source_completion_provider, G_TYPE_OBJECT)
-/* Default implementations */
-static gchar *
-gtk_source_completion_provider_get_name_default (GtkSourceCompletionProvider *provider)
-{
- g_return_val_if_reached (NULL);
-}
-
-static GdkTexture *
-gtk_source_completion_provider_get_icon_default (GtkSourceCompletionProvider *provider)
+static void
+fallback_populate_async (GtkSourceCompletionProvider *provider,
+ GtkSourceCompletionContext *context,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
{
- return NULL;
-}
+ GListModel *ret;
+ GError *error = NULL;
+ GTask *task;
-static const gchar *
-gtk_source_completion_provider_get_icon_name_default (GtkSourceCompletionProvider *provider)
-{
- return NULL;
-}
+ task = g_task_new (provider, cancellable, callback, user_data);
+ g_task_set_source_tag (task, fallback_populate_async);
-static GIcon *
-gtk_source_completion_provider_get_gicon_default (GtkSourceCompletionProvider *provider)
-{
- return NULL;
-}
+ ret = GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (provider)->populate (provider, context, &error);
-static void
-gtk_source_completion_provider_populate_default (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionContext *context)
-{
- gtk_source_completion_context_add_proposals (context, provider, NULL, TRUE);
-}
+ if (ret == NULL)
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ }
+ else
+ {
+ g_task_return_pointer (task, g_steal_pointer (&ret), g_object_unref);
+ }
-static GtkSourceCompletionActivation
-gtk_source_completion_provider_get_activation_default (GtkSourceCompletionProvider *provider)
-{
- return GTK_SOURCE_COMPLETION_ACTIVATION_INTERACTIVE |
- GTK_SOURCE_COMPLETION_ACTIVATION_USER_REQUESTED;
+ g_clear_object (&task);
}
-static gboolean
-gtk_source_completion_provider_match_default (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionContext *context)
+static GListModel *
+fallback_populate_finish (GtkSourceCompletionProvider *provider,
+ GAsyncResult *result,
+ GError **error)
{
- return TRUE;
+ return g_task_propagate_pointer (G_TASK (result), error);
}
-static GtkWidget *
-gtk_source_completion_provider_get_info_widget_default (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionProposal *proposal)
+static GListModel *
+fallback_populate (GtkSourceCompletionProvider *provider,
+ GtkSourceCompletionContext *context,
+ GError **error)
{
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ "Not supported");
return NULL;
}
-static void
-gtk_source_completion_provider_update_info_default (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionProposal *proposal,
- GtkWidget *widget)
-{
-}
-
static gboolean
-gtk_source_completion_provider_get_start_iter_default (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionContext *context,
- GtkSourceCompletionProposal *proposal,
- GtkTextIter *iter)
+fallback_refilter (GtkSourceCompletionProvider *provider,
+ GtkSourceCompletionContext *context,
+ GListModel *model)
{
return FALSE;
}
-static gboolean
-gtk_source_completion_provider_activate_proposal_default (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionProposal *proposal,
- GtkTextIter *iter)
+static void
+fallback_activate (GtkSourceCompletionProvider *provider,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProposal *proposal)
{
- return FALSE;
}
-static gint
-gtk_source_completion_provider_get_interactive_delay_default (GtkSourceCompletionProvider *provider)
+static void
+gtk_source_completion_provider_default_init (GtkSourceCompletionProviderInterface *iface)
{
- /* -1 means the default value in the completion object */
- return -1;
+ iface->populate_async = fallback_populate_async;
+ iface->populate_finish = fallback_populate_finish;
+ iface->populate = fallback_populate;
+ iface->refilter = fallback_refilter;
+ iface->activate = fallback_activate;
}
-static gint
-gtk_source_completion_provider_get_priority_default (GtkSourceCompletionProvider *provider)
+GdkPaintable *
+gtk_source_completion_provider_get_paintable (GtkSourceCompletionProvider *self)
{
- return 0;
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self), NULL);
+
+ return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->get_paintable (self);
}
-static void
-gtk_source_completion_provider_default_init (GtkSourceCompletionProviderInterface *iface)
+char *
+gtk_source_completion_provider_get_title (GtkSourceCompletionProvider *self)
{
- iface->get_name = gtk_source_completion_provider_get_name_default;
-
- iface->get_icon = gtk_source_completion_provider_get_icon_default;
- iface->get_icon_name = gtk_source_completion_provider_get_icon_name_default;
- iface->get_gicon = gtk_source_completion_provider_get_gicon_default;
-
- iface->populate = gtk_source_completion_provider_populate_default;
-
- iface->match = gtk_source_completion_provider_match_default;
- iface->get_activation = gtk_source_completion_provider_get_activation_default;
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self), NULL);
- iface->get_info_widget = gtk_source_completion_provider_get_info_widget_default;
- iface->update_info = gtk_source_completion_provider_update_info_default;
+ if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->get_title)
+ return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->get_title (self);
- iface->get_start_iter = gtk_source_completion_provider_get_start_iter_default;
- iface->activate_proposal = gtk_source_completion_provider_activate_proposal_default;
-
- iface->get_interactive_delay = gtk_source_completion_provider_get_interactive_delay_default;
- iface->get_priority = gtk_source_completion_provider_get_priority_default;
+ return NULL;
}
-/**
- * gtk_source_completion_provider_get_name:
- * @provider: a #GtkSourceCompletionProvider.
- *
- * Get the name of the provider. This should be a translatable name for
- * display to the user. For example: _("Document word completion provider"). The
- * returned string must be freed with g_free().
- *
- * Returns: a new string containing the name of the provider.
- */
-gchar *
-gtk_source_completion_provider_get_name (GtkSourceCompletionProvider *provider)
+int
+gtk_source_completion_provider_get_priority (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context)
{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider), NULL);
-
- return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (provider)->get_name (provider);
-}
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self), 0);
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), 0);
-/**
- * gtk_source_completion_provider_get_icon:
- * @provider: The #GtkSourceCompletionProvider
- *
- * Get the #GdkTexture for the icon of the @provider.
- *
- * Returns: (nullable) (transfer none): The icon to be used for the provider,
- * or %NULL if the provider does not have a special icon.
- */
-GdkTexture *
-gtk_source_completion_provider_get_icon (GtkSourceCompletionProvider *provider)
-{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider), NULL);
+ if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->get_priority)
+ return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->get_priority (self, context);
- return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (provider)->get_icon (provider);
+ return 0;
}
-/**
- * gtk_source_completion_provider_get_icon_name:
- * @provider: The #GtkSourceCompletionProvider
- *
- * Gets the icon name of @provider.
- *
- * Returns: (nullable) (transfer none): The icon name to be used for the provider,
- * or %NULL if the provider does not have a special icon.
- *
- * Since: 3.18
- */
-const gchar *
-gtk_source_completion_provider_get_icon_name (GtkSourceCompletionProvider *provider)
+gboolean
+gtk_source_completion_provider_is_trigger (GtkSourceCompletionProvider *self,
+ const GtkTextIter *iter,
+ gunichar ch)
{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider), NULL);
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self), FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
- return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (provider)->get_icon_name (provider);
+ if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->is_trigger)
+ return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->is_trigger (self, iter, ch);
+
+ return FALSE;
}
-/**
- * gtk_source_completion_provider_get_gicon:
- * @provider: The #GtkSourceCompletionProvider
- *
- * Gets the #GIcon for the icon of @provider.
- *
- * Returns: (nullable) (transfer none): The icon to be used for the provider,
- * or %NULL if the provider does not have a special icon.
- *
- * Since: 3.18
- */
-GIcon *
-gtk_source_completion_provider_get_gicon (GtkSourceCompletionProvider *provider)
+gboolean
+gtk_source_completion_provider_key_activates (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProposal *proposal,
+ guint keyval,
+ GdkModifierType state)
{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider), NULL);
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self), FALSE);
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), FALSE);
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), FALSE);
+
+ if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->key_activates)
+ return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->key_activates (self, context,
proposal, keyval, state);
- return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (provider)->get_gicon (provider);
+ return FALSE;
}
-/**
- * gtk_source_completion_provider_populate:
- * @provider: a #GtkSourceCompletionProvider.
- * @context: a #GtkSourceCompletionContext.
- *
- * Populate @context with proposals from @provider added with the
- * gtk_source_completion_context_add_proposals() function.
- */
void
-gtk_source_completion_provider_populate (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionContext *context)
+gtk_source_completion_provider_populate_async (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
{
- g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self));
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
- GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (provider)->populate (provider, context);
+ GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->populate_async (self, context, cancellable,
callback, user_data);
}
-/**
- * gtk_source_completion_provider_get_activation:
- * @provider: a #GtkSourceCompletionProvider.
- *
- * Get with what kind of activation the provider should be activated.
- *
- * Returns: a combination of #GtkSourceCompletionActivation.
- **/
-GtkSourceCompletionActivation
-gtk_source_completion_provider_get_activation (GtkSourceCompletionProvider *provider)
+GListModel *
+gtk_source_completion_provider_populate_finish (GtkSourceCompletionProvider *self,
+ GAsyncResult *result,
+ GError **error)
{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider),
GTK_SOURCE_COMPLETION_ACTIVATION_NONE);
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self), NULL);
- return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (provider)->get_activation (provider);
+ return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->populate_finish (self, result, error);
}
-/**
- * gtk_source_completion_provider_match:
- * @provider: a #GtkSourceCompletionProvider.
- * @context: a #GtkSourceCompletionContext.
- *
- * Get whether the provider match the context of completion detailed in
- * @context.
- *
- * Returns: %TRUE if @provider matches the completion context, %FALSE otherwise.
- */
gboolean
-gtk_source_completion_provider_match (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionContext *context)
+gtk_source_completion_provider_refilter (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GListModel *model)
{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider), TRUE);
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self), FALSE);
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), FALSE);
+ g_return_val_if_fail (G_IS_LIST_MODEL (model), FALSE);
- return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (provider)->match (provider, context);
-}
+ if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->refilter)
+ return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->refilter (self, context, model);
-/**
- * gtk_source_completion_provider_get_info_widget:
- * @provider: a #GtkSourceCompletionProvider.
- * @proposal: a currently selected #GtkSourceCompletionProposal.
- *
- * Get a customized info widget to show extra information of a proposal.
- * This allows for customized widgets on a proposal basis, although in general
- * providers will have the same custom widget for all their proposals and
- * @proposal can be ignored. The implementation of this function is optional.
- *
- * If this function is not implemented, the default widget is a #GtkLabel. The
- * return value of gtk_source_completion_proposal_get_info() is used as the
- * content of the #GtkLabel.
- *
- * <note>
- * <para>
- * If implemented, gtk_source_completion_provider_update_info()
- * <emphasis>must</emphasis> also be implemented.
- * </para>
- * </note>
- *
- * Returns: (nullable) (transfer none): a custom #GtkWidget to show extra
- * information about @proposal, or %NULL if the provider does not have a special
- * info widget.
- */
-GtkWidget *
-gtk_source_completion_provider_get_info_widget (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionProposal *proposal)
-{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider), NULL);
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), NULL);
-
- return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (provider)->get_info_widget (provider, proposal);
+ return FALSE;
}
-/**
- * gtk_source_completion_provider_update_info:
- * @provider: a #GtkSourceCompletionProvider.
- * @proposal: a #GtkSourceCompletionProposal.
- * @widget: a #GtkWidget provided from gtk_source_completion_provider_get_info_widget()
- *
- * Update extra information shown in @info for @proposal.
- *
- * <note>
- * <para>
- * This function <emphasis>must</emphasis> be implemented when
- * gtk_source_completion_provider_get_info_widget() is implemented.
- * </para>
- * </note>
- */
void
-gtk_source_completion_provider_update_info (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionProposal *proposal,
- GtkWidget *widget)
+gtk_source_completion_provider_display (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProposal *proposal,
+ GtkSourceCompletionCell *cell)
{
- g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self));
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal));
- g_return_if_fail (GTK_IS_WIDGET (widget));
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CELL (cell));
- GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (provider)->update_info (provider, proposal, widget);
+ if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->display)
+ GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->display (self, context, proposal, cell);
}
-/**
- * gtk_source_completion_provider_get_start_iter:
- * @provider: a #GtkSourceCompletionProvider.
- * @proposal: a #GtkSourceCompletionProposal.
- * @context: a #GtkSourceCompletionContext.
- * @iter: (out): a #GtkTextIter.
- *
- * Get the #GtkTextIter at which the completion for @proposal starts. When
- * implemented, this information is used to position the completion window
- * accordingly when a proposal is selected in the completion window. The
- * @proposal text inside the completion window is aligned on @iter.
- *
- * If this function is not implemented, the word boundary is taken to position
- * the completion window. See gtk_source_completion_provider_activate_proposal()
- * for an explanation on the word boundaries.
- *
- * When the @proposal is activated, the default handler uses @iter as the start
- * of the word to replace. See
- * gtk_source_completion_provider_activate_proposal() for more information.
- *
- * Returns: %TRUE if @iter was set for @proposal, %FALSE otherwise.
- */
-gboolean
-gtk_source_completion_provider_get_start_iter (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionContext *context,
- GtkSourceCompletionProposal *proposal,
- GtkTextIter *iter)
+void
+gtk_source_completion_provider_activate (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProposal *proposal)
{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider), FALSE);
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), FALSE);
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), FALSE);
- g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self));
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
+ g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal));
- return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (provider)->get_start_iter (provider, context,
proposal, iter);
+ if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->activate)
+ GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->activate (self, context, proposal);
}
/**
- * gtk_source_completion_provider_activate_proposal:
- * @provider: a #GtkSourceCompletionProvider.
- * @proposal: a #GtkSourceCompletionProposal.
- * @iter: a #GtkTextIter.
+ * gtk_source_completion_provider_list_alternates:
+ * @self: a #GtkSourceCompletionProvider
+ * @content: a #GtkSourceCompletionContext
+ * @proposal: a #GtkSourceCompletionProposal
*
- * Activate @proposal at @iter. When this functions returns %FALSE, the default
- * activation of @proposal will take place which replaces the word at @iter
- * with the text of @proposal (see gtk_source_completion_proposal_get_text()).
+ * Providers should return a list of alternates to @proposal or %NULL if
+ * there are no alternates available. This can be used by the completion
+ * view to allow the user to move laterally through similar proposals, such
+ * as overrides of methods by the same name.
*
- * Here is how the default activation selects the boundaries of the word to
- * replace. The end of the word is @iter. For the start of the word, it depends
- * on whether a start iter is defined for @proposal (see
- * gtk_source_completion_provider_get_start_iter()). If a start iter is defined,
- * the start of the word is the start iter. Else, the word (as long as possible)
- * will contain only alphanumerical and the "_" characters.
+ * Returns: (nullable) (transfer full) (element-type GtkSourceCompletionProposal):
+ * a #GPtrArray of #GtkSourceCompletionProposal or %NULL.
*
- * Returns: %TRUE to indicate that the proposal activation has been handled,
- * %FALSE otherwise.
+ * Since: 5.0
*/
-gboolean
-gtk_source_completion_provider_activate_proposal (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionProposal *proposal,
- GtkTextIter *iter)
-{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider), FALSE);
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), FALSE);
-
- return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (provider)->activate_proposal (provider, proposal,
iter);
-}
-
-/**
- * gtk_source_completion_provider_get_interactive_delay:
- * @provider: a #GtkSourceCompletionProvider.
- *
- * Get the delay in milliseconds before starting interactive completion for
- * this provider. A value of -1 indicates to use the default value as set
- * by the #GtkSourceCompletion:auto-complete-delay property.
- *
- * Returns: the interactive delay in milliseconds.
- **/
-gint
-gtk_source_completion_provider_get_interactive_delay (GtkSourceCompletionProvider *provider)
+GPtrArray *
+gtk_source_completion_provider_list_alternates (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProposal *proposal)
{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider), -1);
-
- return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (provider)->get_interactive_delay (provider);
-}
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self), NULL);
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), NULL);
+ g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), NULL);
-/**
- * gtk_source_completion_provider_get_priority:
- * @provider: a #GtkSourceCompletionProvider.
- *
- * Get the provider priority. The priority determines the order in which
- * proposals appear in the completion popup. Higher priorities are sorted
- * before lower priorities. The default priority is 0.
- *
- * Returns: the provider priority.
- **/
-gint
-gtk_source_completion_provider_get_priority (GtkSourceCompletionProvider *provider)
-{
- g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider), 0);
+ if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->list_alternates)
+ return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->list_alternates (self, context,
proposal);
- return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (provider)->get_priority (provider);
+ return NULL;
}
diff --git a/gtksourceview/gtksourcecompletionprovider.h b/gtksourceview/gtksourcecompletionprovider.h
index 0ece40ae..32f2444a 100644
--- a/gtksourceview/gtksourcecompletionprovider.h
+++ b/gtksourceview/gtksourcecompletionprovider.h
@@ -1,8 +1,7 @@
/*
* This file is part of GtkSourceView
*
- * Copyright 2007 - 2009 Jesús Barbero Rodríguez <chuchiperriman gmail com>
- * Copyright 2009 - Jesse van den Kieboom <jessevdk gnome org>
+ * Copyright 2020 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
@@ -16,122 +15,106 @@
*
* 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 "gtksourcecompletioncontext.h"
#include "gtksourcetypes.h"
G_BEGIN_DECLS
#define GTK_SOURCE_TYPE_COMPLETION_PROVIDER (gtk_source_completion_provider_get_type())
-GTK_SOURCE_AVAILABLE_IN_ALL
+GTK_SOURCE_AVAILABLE_IN_5_0
G_DECLARE_INTERFACE (GtkSourceCompletionProvider, gtk_source_completion_provider, GTK_SOURCE,
COMPLETION_PROVIDER, GObject)
-/**
- * GtkSourceCompletionProviderInterface:
- * @g_iface: The parent interface.
- * @get_name: The virtual function pointer for gtk_source_completion_provider_get_name().
- * Must be implemented.
- * @get_icon: The virtual function pointer for gtk_source_completion_provider_get_icon().
- * By default, %NULL is returned.
- * @get_icon_name: The virtual function pointer for gtk_source_completion_provider_get_icon_name().
- * By default, %NULL is returned.
- * @get_gicon: The virtual function pointer for gtk_source_completion_provider_get_gicon().
- * By default, %NULL is returned.
- * @populate: The virtual function pointer for gtk_source_completion_provider_populate().
- * Add no proposals by default.
- * @match: The virtual function pointer for gtk_source_completion_provider_match().
- * By default, %TRUE is returned.
- * @get_activation: The virtual function pointer for gtk_source_completion_provider_get_activation().
- * The combination of all #GtkSourceCompletionActivation is returned by default.
- * @get_info_widget: The virtual function pointer for gtk_source_completion_provider_get_info_widget().
- * By default, %NULL is returned.
- * @update_info: The virtual function pointer for gtk_source_completion_provider_update_info().
- * Does nothing by default.
- * @get_start_iter: The virtual function pointer for gtk_source_completion_provider_get_start_iter().
- * By default, %FALSE is returned.
- * @activate_proposal: The virtual function pointer for gtk_source_completion_provider_activate_proposal().
- * By default, %FALSE is returned.
- * @get_interactive_delay: The virtual function pointer for
gtk_source_completion_provider_get_interactive_delay().
- * By default, -1 is returned.
- * @get_priority: The virtual function pointer for gtk_source_completion_provider_get_priority().
- * By default, 0 is returned.
- *
- * The virtual function table for #GtkSourceCompletionProvider.
- */
struct _GtkSourceCompletionProviderInterface
{
- GTypeInterface g_iface;
+ GTypeInterface parent_iface;
- gchar *(*get_name) (GtkSourceCompletionProvider *provider);
- GdkTexture *(*get_icon) (GtkSourceCompletionProvider *provider);
- const gchar *(*get_icon_name) (GtkSourceCompletionProvider *provider);
- GIcon *(*get_gicon) (GtkSourceCompletionProvider *provider);
- void (*populate) (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionContext *context);
- gboolean (*match) (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionContext *context);
- GtkSourceCompletionActivation (*get_activation) (GtkSourceCompletionProvider *provider);
- GtkWidget *(*get_info_widget) (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionProposal *proposal);
- void (*update_info) (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionProposal *proposal,
- GtkWidget *widget);
- gboolean (*get_start_iter) (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionContext *context,
- GtkSourceCompletionProposal *proposal,
- GtkTextIter *iter);
- gboolean (*activate_proposal) (GtkSourceCompletionProvider *provider,
- GtkSourceCompletionProposal *proposal,
- GtkTextIter *iter);
- gint (*get_interactive_delay) (GtkSourceCompletionProvider *provider);
- gint (*get_priority) (GtkSourceCompletionProvider *provider);
+ GdkPaintable *(*get_paintable) (GtkSourceCompletionProvider *self);
+ char *(*get_title) (GtkSourceCompletionProvider *self);
+ int (*get_priority) (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context);
+ gboolean (*is_trigger) (GtkSourceCompletionProvider *self,
+ const GtkTextIter *iter,
+ gunichar ch);
+ gboolean (*key_activates) (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProposal *proposal,
+ guint keyval,
+ GdkModifierType state);
+ GListModel *(*populate) (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GError **error);
+ void (*populate_async) (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ GListModel *(*populate_finish) (GtkSourceCompletionProvider *self,
+ GAsyncResult *result,
+ GError **error);
+ gboolean (*refilter) (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GListModel *model);
+ void (*display) (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProposal *proposal,
+ GtkSourceCompletionCell *cell);
+ void (*activate) (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProposal *proposal);
+ GPtrArray *(*list_alternates) (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProposal *proposal);
};
-GTK_SOURCE_AVAILABLE_IN_ALL
-gchar *gtk_source_completion_provider_get_name
(GtkSourceCompletionProvider *provider);
-GTK_SOURCE_AVAILABLE_IN_ALL
-GdkTexture *gtk_source_completion_provider_get_icon
(GtkSourceCompletionProvider *provider);
-GTK_SOURCE_AVAILABLE_IN_3_18
-const gchar *gtk_source_completion_provider_get_icon_name
(GtkSourceCompletionProvider *provider);
-GTK_SOURCE_AVAILABLE_IN_3_18
-GIcon *gtk_source_completion_provider_get_gicon
(GtkSourceCompletionProvider *provider);
-GTK_SOURCE_AVAILABLE_IN_ALL
-void gtk_source_completion_provider_populate
(GtkSourceCompletionProvider *provider,
-
GtkSourceCompletionContext *context);
-GTK_SOURCE_AVAILABLE_IN_ALL
-GtkSourceCompletionActivation gtk_source_completion_provider_get_activation
(GtkSourceCompletionProvider *provider);
-GTK_SOURCE_AVAILABLE_IN_ALL
-gboolean gtk_source_completion_provider_match
(GtkSourceCompletionProvider *provider,
-
GtkSourceCompletionContext *context);
-GTK_SOURCE_AVAILABLE_IN_ALL
-GtkWidget *gtk_source_completion_provider_get_info_widget
(GtkSourceCompletionProvider *provider,
-
GtkSourceCompletionProposal *proposal);
-GTK_SOURCE_AVAILABLE_IN_ALL
-void gtk_source_completion_provider_update_info
(GtkSourceCompletionProvider *provider,
-
GtkSourceCompletionProposal *proposal,
- GtkWidget
*widget);
-GTK_SOURCE_AVAILABLE_IN_ALL
-gboolean gtk_source_completion_provider_get_start_iter
(GtkSourceCompletionProvider *provider,
-
GtkSourceCompletionContext *context,
-
GtkSourceCompletionProposal *proposal,
- GtkTextIter
*iter);
-GTK_SOURCE_AVAILABLE_IN_ALL
-gboolean gtk_source_completion_provider_activate_proposal
(GtkSourceCompletionProvider *provider,
-
GtkSourceCompletionProposal *proposal,
- GtkTextIter
*iter);
-GTK_SOURCE_AVAILABLE_IN_ALL
-gint gtk_source_completion_provider_get_interactive_delay
(GtkSourceCompletionProvider *provider);
-GTK_SOURCE_AVAILABLE_IN_ALL
-gint gtk_source_completion_provider_get_priority
(GtkSourceCompletionProvider *provider);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GdkPaintable *gtk_source_completion_provider_get_paintable (GtkSourceCompletionProvider *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+char *gtk_source_completion_provider_get_title (GtkSourceCompletionProvider *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+int gtk_source_completion_provider_get_priority (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context);
+GTK_SOURCE_AVAILABLE_IN_5_0
+gboolean gtk_source_completion_provider_is_trigger (GtkSourceCompletionProvider *self,
+ const GtkTextIter *iter,
+ gunichar ch);
+GTK_SOURCE_AVAILABLE_IN_5_0
+gboolean gtk_source_completion_provider_key_activates (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProposal *proposal,
+ guint keyval,
+ GdkModifierType state);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_provider_populate_async (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GListModel *gtk_source_completion_provider_populate_finish (GtkSourceCompletionProvider *self,
+ GAsyncResult *result,
+ GError **error);
+GTK_SOURCE_AVAILABLE_IN_5_0
+gboolean gtk_source_completion_provider_refilter (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GListModel *model);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_provider_display (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProposal *proposal,
+ GtkSourceCompletionCell *cell);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void gtk_source_completion_provider_activate (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProposal *proposal);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GPtrArray *gtk_source_completion_provider_list_alternates (GtkSourceCompletionProvider *self,
+ GtkSourceCompletionContext *context,
+ GtkSourceCompletionProposal *proposal);
G_END_DECLS
diff --git a/gtksourceview/gtksourcetypes-private.h b/gtksourceview/gtksourcetypes-private.h
index 97efe327..8cc31af1 100644
--- a/gtksourceview/gtksourcetypes-private.h
+++ b/gtksourceview/gtksourcetypes-private.h
@@ -23,10 +23,14 @@
G_BEGIN_DECLS
+typedef struct _GtkSourceAssistant GtkSourceAssistant;
+typedef struct _GtkSourceAssistantChild GtkSourceAssistantChild;
typedef struct _GtkSourceBufferInputStream GtkSourceBufferInputStream;
typedef struct _GtkSourceBufferOutputStream GtkSourceBufferOutputStream;
typedef struct _GtkSourceCompletionInfo GtkSourceCompletionInfo;
-typedef struct _GtkSourceCompletionModel GtkSourceCompletionModel;
+typedef struct _GtkSourceCompletionList GtkSourceCompletionList;
+typedef struct _GtkSourceCompletionListBox GtkSourceCompletionListBox;
+typedef struct _GtkSourceCompletionListBoxRow GtkSourceCompletionListBoxRow;
typedef struct _GtkSourceContextEngine GtkSourceContextEngine;
typedef struct _GtkSourceEngine GtkSourceEngine;
typedef struct _GtkSourceGutterRendererLines GtkSourceGutterRendererLines;
diff --git a/gtksourceview/gtksourcetypes.h b/gtksourceview/gtksourcetypes.h
index 9c7a6b70..5a313fa9 100644
--- a/gtksourceview/gtksourcetypes.h
+++ b/gtksourceview/gtksourcetypes.h
@@ -36,9 +36,9 @@ G_BEGIN_DECLS
*/
typedef struct _GtkSourceBuffer GtkSourceBuffer;
-typedef struct _GtkSourceCompletionContext GtkSourceCompletionContext;
typedef struct _GtkSourceCompletion GtkSourceCompletion;
-typedef struct _GtkSourceCompletionItem GtkSourceCompletionItem;
+typedef struct _GtkSourceCompletionCell GtkSourceCompletionCell;
+typedef struct _GtkSourceCompletionContext GtkSourceCompletionContext;
typedef struct _GtkSourceCompletionProposal GtkSourceCompletionProposal;
typedef struct _GtkSourceCompletionProvider GtkSourceCompletionProvider;
typedef struct _GtkSourceEncoding GtkSourceEncoding;
diff --git a/gtksourceview/gtksourceview.c b/gtksourceview/gtksourceview.c
index 95a8b292..ac8c13be 100644
--- a/gtksourceview/gtksourceview.c
+++ b/gtksourceview/gtksourceview.c
@@ -311,6 +311,21 @@ static gboolean gtk_source_view_rgba_drop (GtkDropTarget
GtkSourceView *view);
static void gtk_source_view_populate_extra_menu (GtkSourceView *view);
+static GtkSourceCompletion *
+get_completion (GtkSourceView *self)
+{
+ GtkSourceViewPrivate *priv = gtk_source_view_get_instance_private (self);
+
+ g_assert (GTK_SOURCE_IS_VIEW (self));
+
+ if (priv->completion == NULL)
+ {
+ priv->completion = _gtk_source_completion_new (self);
+ }
+
+ return priv->completion;
+}
+
static void
gtk_source_view_constructed (GObject *object)
{
@@ -1661,15 +1676,8 @@ set_source_buffer (GtkSourceView *view,
static void
gtk_source_view_show_completion_real (GtkSourceView *view)
{
- GtkSourceCompletion *completion;
- GtkSourceCompletionContext *context;
-
- completion = gtk_source_view_get_completion (view);
- context = gtk_source_completion_create_context (completion, NULL);
-
- gtk_source_completion_start (completion,
- gtk_source_completion_get_providers (completion),
- context);
+ GtkSourceCompletion *completion = get_completion (view);
+ gtk_source_completion_show (completion);
}
static void
diff --git a/gtksourceview/gtksourceview.gresource.xml b/gtksourceview/gtksourceview.gresource.xml
index 9321b920..208ab91b 100644
--- a/gtksourceview/gtksourceview.gresource.xml
+++ b/gtksourceview/gtksourceview.gresource.xml
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/gtksourceview/ui">
- <file preprocess="xml-stripblanks">gtksourcecompletion.ui</file>
<file preprocess="xml-stripblanks">gtksourcestyleschemechooserwidget.ui</file>
</gresource>
</gresources>
diff --git a/gtksourceview/meson.build b/gtksourceview/meson.build
index fc3a9cba..310ab38c 100644
--- a/gtksourceview/meson.build
+++ b/gtksourceview/meson.build
@@ -9,10 +9,10 @@ core_public_h = files([
'gtksource.h',
'gtksourcebuffer.h',
'gtksourcecompletion.h',
+ 'gtksourcecompletioncell.h',
'gtksourcecompletioncontext.h',
- 'gtksourcecompletionitem.h',
- 'gtksourcecompletionproposal.h',
'gtksourcecompletionprovider.h',
+ 'gtksourcecompletionproposal.h',
'gtksourceencoding.h',
'gtksourcefile.h',
'gtksourcefileloader.h',
@@ -48,10 +48,10 @@ core_public_h = files([
core_public_c = files([
'gtksourcebuffer.c',
'gtksourcecompletion.c',
+ 'gtksourcecompletioncell.c',
'gtksourcecompletioncontext.c',
- 'gtksourcecompletionitem.c',
- 'gtksourcecompletionproposal.c',
'gtksourcecompletionprovider.c',
+ 'gtksourcecompletionproposal.c',
'gtksourceencoding.c',
'gtksourcefile.c',
'gtksourcefileloader.c',
@@ -92,7 +92,9 @@ core_private_c = files([
'gtksourcebufferinternal.c',
'gtksourcebufferoutputstream.c',
'gtksourcecompletioninfo.c',
- 'gtksourcecompletionmodel.c',
+ 'gtksourcecompletionlist.c',
+ 'gtksourcecompletionlistbox.c',
+ 'gtksourcecompletionlistboxrow.c',
'gtksourcecontextengine.c',
'gtksourceengine.c',
'gtksourcegutterrendererlines.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]