After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 786151 - Completion Provider: Vim word Completion
Completion Provider: Vim word Completion
Status: RESOLVED FIXED
Product: gnome-builder
Classification: Other
Component: editor
unspecified
Other Linux
: Normal enhancement
: ---
Assigned To: Umang Jain
GNOME Builder Maintainers
Depends on:
Blocks:
 
 
Reported: 2017-08-11 12:54 UTC by Umang Jain
Modified: 2017-09-02 01:22 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
Add Vim Words as completion provider (18.75 KB, patch)
2017-08-11 12:56 UTC, Umang Jain
none Details | Review
Add VimWordsProposal (9.23 KB, patch)
2017-08-11 12:56 UTC, Umang Jain
none Details | Review
Makefile plumbing (5.03 KB, patch)
2017-08-11 12:57 UTC, Umang Jain
none Details | Review
Add Vim Words as completion provider (21.58 KB, patch)
2017-08-20 23:06 UTC, Umang Jain
none Details | Review
Add VimWordsProposal (10.15 KB, patch)
2017-08-20 23:07 UTC, Umang Jain
none Details | Review
Add IdeWordCompletionProvider (22.41 KB, patch)
2017-08-25 20:46 UTC, Umang Jain
none Details | Review
Add IdeWordCompletionResults (7.46 KB, patch)
2017-08-25 20:47 UTC, Umang Jain
none Details | Review
Add IdeWordCompletionItem (7.46 KB, patch)
2017-08-25 20:48 UTC, Umang Jain
none Details | Review
Add IdeWordCompletionResults (5.45 KB, patch)
2017-08-25 20:48 UTC, Umang Jain
accepted-commit_now Details | Review
Add word completion provider (38.91 KB, patch)
2017-08-31 01:14 UTC, Umang Jain
none Details | Review
sourceview: Add begin-word-completion signal (4.79 KB, patch)
2017-08-31 01:17 UTC, Umang Jain
committed Details | Review
Improve on how to replay results (10.08 KB, patch)
2017-08-31 01:18 UTC, Umang Jain
none Details | Review
word-completion-provider: Remove offset computation logic (3.64 KB, patch)
2017-08-31 01:18 UTC, Umang Jain
none Details | Review
Improve on how to replay results (11.86 KB, patch)
2017-08-31 08:39 UTC, Umang Jain
committed Details | Review
word-completion-provider: Remove offset computation logic (4.59 KB, patch)
2017-08-31 08:40 UTC, Umang Jain
committed Details | Review

Description Umang Jain 2017-08-11 12:54:59 UTC
Vim like word completion from GtkSourceView
Comment 1 Umang Jain 2017-08-11 12:56:07 UTC
Created attachment 357408 [details] [review]
Add Vim Words as completion provider
Comment 2 Umang Jain 2017-08-11 12:56:33 UTC
Created attachment 357409 [details] [review]
Add VimWordsProposal
Comment 3 Umang Jain 2017-08-11 12:57:41 UTC
Created attachment 357411 [details] [review]
Makefile plumbing
Comment 4 Christian Hergert 2017-08-14 20:53:20 UTC
Review of attachment 357408 [details] [review]:

I think this is still missing the primary focus we want, which is to have distance from cursor taken into account?

It looks like you have the hashtable for quick access to what has been found and you may need a second data structure to keep things sorted by their offset from the cursor.

::: gtksourceview/completion-providers/vim-words/gtksourcecompletionvimwords.c
@@ +54,3 @@
+  GHashTable *all_proposals;
+  gchar *word;
+  guint cancel_id;

Signal connections are gulong, not guint.

@@ +77,3 @@
+  gunichar ch = 0;
+
+  g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), NULL);

g_assert() for static functions.

@@ +93,3 @@
+
+  if (ch && !g_unichar_isalnum (ch) && (ch != '_'))
+    gtk_text_iter_forward_char (&begin);

This is an infinite loop if you reach the end of the buffer. You need to break if forward_char() returns non-TRUE

@@ +115,3 @@
+                                                &match_end,
+                                                &has_wrapped_around,
+                                                &error))

Nothing cleans up &error, so it is leaked. If you don't need the error, just pass NULL instead.

@@ +125,3 @@
+          proposal = gtk_source_completion_vim_words_proposal_new (text);
+
+          g_hash_table_insert (self->priv->all_proposals, g_strdup (text), g_object_ref (proposal));

Proposal is leaked here because you already own the new reference and the increment it to add it to the hash table.

@@ +136,3 @@
+    }
+
+  list = g_hash_table_get_values (self->priv->all_proposals);

If we are just taking the list of values from the hash table, how are we preserving the distance from the insertion cursor?

@@ +159,3 @@
+static void
+population_finished (GtkSourceCompletionVimWords *self)
+{

Add precondition check and include all the function parameters from the signal (and ensure they are correct too).

@@ +182,3 @@
+  GtkTextIter iter;
+
+  // NOTE: Return if the completion is not USER_REQUESTED

Since you specify the activation, there is no need for a USER_REQUESTED check. the base match() should do that for you.

@@ +194,3 @@
+  g_hash_table_remove_all (self->priv->all_proposals);
+
+  self->priv->search_settings = gtk_source_search_settings_new ();

Add some assertions that these are NULL before-hand so that we know that the population_finished callback has cleaned things up first.

@@ +207,3 @@
+  gtk_source_search_settings_set_search_text (self->priv->search_settings, self->priv->word);
+
+  self->priv->cancel_id = g_signal_connect_swapped (context, "cancelled", G_CALLBACK (population_finished), self);

Is cancelled guaranteed to be called before the next populate() call is made? Instead of calling population_finished(), add a callback function with the proper signature and precondition checks which then calls population_finished().

@@ +323,3 @@
+  g_clear_object (&self->priv->icon);
+  g_clear_object (&self->priv->search_context);
+  g_clear_pointer (&self->priv->all_proposals, (GDestroyNotify) g_hash_table_unref);

I don't think the cast is necessary here?

@@ +410,3 @@
+     * The type of activation.
+     *
+     * Since: 3.10

Remove the gtk-doc or update it.

@@ +432,3 @@
+
+GtkSourceCompletionVimWords *
+gtk_source_completion_vim_words_new (const gchar *name, GdkPixbuf   *icon)

Use a GIcon instead of a GdkPixbuf and then implement ->get_gicon().

::: gtksourceview/completion-providers/vim-words/gtksourcecompletionvimwords.h
@@ +57,3 @@
+
+GTK_SOURCE_AVAILABLE_IN_ALL
+GtkSourceCompletionVimWords *gtk_source_completion_vim_words_new (const gchar *name, GdkPixbuf *icon);

A GdkPixbuf is a rendered framebuffer of an icon. We probably don't want to pass those around as they would become invalid if you move the window between a HiDPI and non-HiDPI monitor. Probably better to use a GIcon here (and re-use them as much as possible to avoid lots of object creations).
Comment 5 Christian Hergert 2017-08-14 20:55:08 UTC
Review of attachment 357409 [details] [review]:

::: gtksourceview/completion-providers/vim-words/gtksourcecompletionvimwordsproposal.h
@@ +58,3 @@
+GTK_SOURCE_INTERNAL
+GtkSourceCompletionVimWordsProposal *
+                 gtk_source_completion_vim_words_proposal_new       (const gchar *word);

Proposals can have a GIcon, so pass that on from the provider.

@@ +63,3 @@
+const gchar     *gtk_source_completion_vim_words_proposal_get_word  (GtkSourceCompletionVimWordsProposal *proposal);
+
+GTK_SOURCE_INTERNAL

I don't think you are using these signals anywhere, so they can be removed.
Comment 6 Christian Hergert 2017-08-14 20:55:54 UTC
Review of attachment 357411 [details] [review]:

Cool, although only necessary if upstream is amenable to having new code in gtksourceview 3.x.
Comment 7 Umang Jain 2017-08-20 18:51:07 UTC
> @@ +207,3 @@
> +  gtk_source_search_settings_set_search_text (self->priv->search_settings,
> self->priv->word);
> +
> +  self->priv->cancel_id = g_signal_connect_swapped (context, "cancelled",
> G_CALLBACK (population_finished), self);
> 
> Is cancelled guaranteed to be called before the next populate() call is
> made? Instead of calling population_finished(), add a callback function with
> the proper signature and precondition checks which then calls
> population_finished().
> 

Can you tell me what preconditions checks are required for e.g. , to call population_finished() subsequently? 

And I think the call is guaranteed before next populate() as in population_finished() we g_clear_object (context), no?
Comment 8 Christian Hergert 2017-08-20 20:04:04 UTC
(In reply to Umang Jain from comment #7)
> Can you tell me what preconditions checks are required for e.g. , to call
> population_finished() subsequently? 
> 
> And I think the call is guaranteed before next populate() as in
> population_finished() we g_clear_object (context), no?

I think we've cleared this up via IRC.
Comment 9 Umang Jain 2017-08-20 23:06:37 UTC
Created attachment 358035 [details] [review]
Add Vim Words as completion provider
Comment 10 Umang Jain 2017-08-20 23:07:20 UTC
Created attachment 358036 [details] [review]
Add VimWordsProposal
Comment 11 Christian Hergert 2017-08-21 18:35:41 UTC
Review of attachment 358036 [details] [review]:

::: gtksourceview/completion-providers/vim-words/gtksourcecompletionvimwordsproposal.c
@@ +35,3 @@
+enum
+{
+  UNUSED,

If you're not using "unused" signal, get rid of it, the functions, signals[], etc.

@@ +109,3 @@
+
+GtkSourceCompletionVimWordsProposal *
+gtk_source_completion_vim_words_proposal_new (const gchar *word, gint offset, GIcon *icon)

One parameter per line and align them.

@@ +122,3 @@
+
+void
+gtk_source_completion_vim_words_proposal_use (GtkSourceCompletionVimWordsProposal *proposal)

Remove this, you aren't using it.

@@ +130,3 @@
+
+void
+gtk_source_completion_vim_words_proposal_unuse (GtkSourceCompletionVimWordsProposal *proposal)

And this.
Comment 12 Christian Hergert 2017-08-21 19:04:57 UTC
Review of attachment 358035 [details] [review]:

It would be nice to see some tests for this so we know it is working as you expect.

::: gtksourceview/completion-providers/vim-words/gtksourcecompletionvimwords.c
@@ +144,3 @@
+        {
+          g_warning ("Unable to get completion proposals: %s", error->message);
+          g_error_free (error);

Don't leave invalid pointers around when possible. g_clear_error (&error);

@@ +157,3 @@
+
+              gtk_text_buffer_get_end_iter (gtk_text_iter_get_buffer (&self->priv->insert_iter), &end_iter);
+              offset = gtk_text_iter_get_offset (&end_iter) - gtk_text_iter_get_offset (&self->priv->insert_iter) +

This would be easier to read if each function call was on it's own line.

@@ +165,3 @@
+
+	  proposal = gtk_source_completion_vim_words_proposal_new (text, offset, NULL);
+	  g_hash_table_insert (self->priv->all_proposals, g_strdup (text), g_object_ref (proposal));

"text" is leaked, so maybe g_steal_pointer (&text) here.

@@ +175,3 @@
+                                               self);
+      return;
+

Missing g_free (text);

@@ +180,3 @@
+finish:
+  list = g_hash_table_get_values (self->priv->all_proposals);
+  list = g_list_sort (list, sort_by_offset);

This is rather expensive to do on every keypress, but once this is using IdeCompletionResults and IdeCompletionItem (as the base proposal class) that won't be much of an issue because we can just replay the previous results and sorting adjustments.

@@ +216,3 @@
+        }
+
+        g_clear_object (&self->priv->context);

Alignment is 2-spaces off

@@ +217,3 @@
+
+        g_clear_object (&self->priv->context);
+    }

Extra newline after braces

@@ +303,3 @@
+      if (!g_unichar_isalnum (ch) && ch != '_')
+        return FALSE;
+    }

Maybe just put the return TRUE inside the if block and return FALSE outside the block without the else.
Comment 13 Umang Jain 2017-08-25 20:46:42 UTC
Created attachment 358444 [details] [review]
Add IdeWordCompletionProvider
Comment 14 Umang Jain 2017-08-25 20:47:25 UTC
Created attachment 358445 [details] [review]
Add IdeWordCompletionResults
Comment 15 Umang Jain 2017-08-25 20:48:16 UTC
Created attachment 358446 [details] [review]
Add IdeWordCompletionItem
Comment 16 Umang Jain 2017-08-25 20:48:48 UTC
Created attachment 358447 [details] [review]
Add IdeWordCompletionResults
Comment 17 Christian Hergert 2017-08-27 07:58:30 UTC
Review of attachment 358446 [details] [review]:

Couple quick fixes

::: libide/sourceview/ide-word-completion-item.c
@@ +19,3 @@
+#define G_LOG_DOMAIN "ide-word-completion-item"
+
+#ifdef HAVE_CONFIG_H

I don't think anything needs config.h here, so remove this

@@ +92,3 @@
+
+IdeWordCompletionItem *
+ide_word_completion_item_new (const gchar *word, gint offset, GIcon *icon)

One parameter per line

@@ +102,3 @@
+  proposal->priv->word = g_strdup (word);
+  proposal->priv->offset = offset;
+  proposal->priv->icon = icon;

This is not safe, you need to own your own reference.

::: libide/sourceview/ide-word-completion-item.h
@@ +26,3 @@
+G_BEGIN_DECLS
+
+#define IDE_TYPE_WORD_COMPLETION_ITEM                   (ide_word_completion_item_get_type ())

For Builder, we should be using the G_DECLARE_FINAL_TYPE() macro instead of the old style declarations.
Comment 18 Christian Hergert 2017-08-27 08:02:51 UTC
Review of attachment 358447 [details] [review]:

Cool. If you are short on time, no need to do the G_DECLARE stuff for your final project submission, but we should do it soon to keep the code-base consistent.

::: libide/sourceview/ide-word-completion-results.h
@@ +26,3 @@
+G_BEGIN_DECLS
+
+#define IDE_TYPE_WORD_COMPLETION_RESULTS            (ide_word_completion_results_get_type())

G_DECLARE_FINAL_TYPE() for this as well. See the other files for examples, but it should come out like:

#define IDE_TYPE_WORD_COMPLETION_RESULTS (ide_word_completion_results_get_type())

G_DECLARE_FINAL_TYPE (IdeWordCompletionResults, ide_word_completion_results, IDE, WORD_COMPLETION_RESULTS, IdeCompletionResults)

and then you'll have to add to your .c file:

struct _IdeWordCompletionResults
{
  IdeCompletionResults parent;
  /* Put stuff here instead of via ->priv */
};
Comment 19 Christian Hergert 2017-08-27 08:30:19 UTC
Review of attachment 358444 [details] [review]:

Few things to fix up, nice work!

::: libide/sourceview/ide-word-completion-provider.c
@@ +19,3 @@
+#define G_LOG_DOMAIN "ide-word-completion-provider"
+
+#ifdef HAVE_CONFIG_H

Drop the HAVE_CONFIG_H stuff unless you're really using config.h (i dont think you are)

@@ +29,3 @@
+#include "sourceview/ide-word-completion-results.h"
+#include "sourceview/ide-completion-provider.h"
+#include "ide-debug.h"

To be like the rest of the code base, toplevel includes like ide-debug.h, ide-object.h, etc go first, and then the others like:

#include "ide-debug.h"

#include "sourceview/ide-word-compltion-provider.h
# ...
# ...

@@ +41,3 @@
+  N_PROPERTIES
+};
+static GParamSpec *properties[N_PROPERTIES];

Extra new line before globals

@@ +73,3 @@
+refresh_iters (IdeWordCompletionProvider *self,
+               GtkTextIter *match_start,
+               GtkTextIter *match_end)

Align parameters like so:

73	refresh_iters (IdeWordCompletionProvider *self,
74	               GtkTextIter               *match_start,
75	               GtkTextIter               *match_end)

@@ +82,3 @@
+  g_assert (IDE_IS_COMPLETION_PROVIDER (self));
+
+  insert_mark = GTK_TEXT_MARK (g_object_get_data (G_OBJECT (self), "insert-mark"));

I don't like using GObject data for stuff like this. You already have the GtkSourceCompletionContext in self->priv, so you can get an iter from that and then the buffer.

if (priv->context == NULL ||
    !gtk_source_completion_context_get_iter (priv->context, &iter))
  return FALSE;

buffer = gtk_text_iter_get_buffer (&iter);

@@ +85,3 @@
+  buffer = gtk_text_mark_get_buffer (insert_mark);
+
+  start_mark = gtk_text_buffer_get_mark (buffer, "start-mark");

Don't use named marks. This will break if you have multiple views who try to do completion and then stomp over each others marks.

Instead, create a textmark with a NULL name, and store it in Private. Then use that pointer later on. If you do this, you can even simplify the code above to just get the buffer from the stashed marks.

@@ +132,3 @@
+      g_assert (buffer != NULL);
+
+      start_mark = gtk_text_buffer_create_mark (buffer, "start-mark", &match_start, FALSE);

Create unnamed marks (use NULL) and save them in Private so that you don't clobber another views marks on the same buffer.

@@ +191,3 @@
+                                               (GAsyncReadyCallback) forward_search_finished,
+                                               self);
+      gtk_text_mark_get_deleted (start_mark);

This function doesn't do anything, perhaps you meant gtk_text_buffer_delete_mark()?

@@ +200,3 @@
+  ide_completion_results_present (IDE_COMPLETION_RESULTS (self->priv->results),
+                                  GTK_SOURCE_COMPLETION_PROVIDER (self), self->priv->context);
+  gtk_text_mark_get_deleted (insert_mark);

Same here

@@ +201,3 @@
+                                  GTK_SOURCE_COMPLETION_PROVIDER (self), self->priv->context);
+  gtk_text_mark_get_deleted (insert_mark);
+  g_hash_table_destroy (self->priv->all_proposals);

always clear destroyed pointers to NULL in the Builder code-base. The extra cycle is worth it to simplify debugging dangling pointers.

@@ +223,3 @@
+  if (self->priv->context != NULL)
+    {
+      if (self->priv->cancel_id)

This condition/block can be replaced with:

ide_clear_signal_handler (self->priv->context, &self->priv->cancel_id);

from "ide-macros.h"

@@ +238,3 @@
+
+static void
+completion_cancelled_cb (IdeWordCompletionProvider *self)

Include the second parameter for this signal handler (GtkSourceCompletionContext *context) and add precondition check for it.

@@ +266,3 @@
+  g_assert (self->priv->search_settings == NULL);
+  g_assert (self->priv->search_context == NULL);
+  g_assert (buffer != NULL);

assert that self->priv->cancel_id == 0) too

@@ +300,3 @@
+  self->priv->results = ide_word_completion_results_new (self->priv->current_word);
+
+  insert_mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer));

Drop this, marks are only useful with buffers and if you have the buffer you can get the insert mark (and there are other ways to get the buffer).

@@ +309,3 @@
+                                           NULL,
+                                           (GAsyncReadyCallback)forward_search_finished,
+                                           self);

You haven't provided a way to guarantee that this async operation completes before your object get's finalized. So instead of self, increment the reference count with

 g_object_ref (self)

and then be sure to drop the reference in all cases from forward_search_finished(). The easiest way is probably to use "gpointer user_data" for the 3rd parameter and then:

 g_autoptr(IdeWordCopmletionProvider) self = user_data;

::: libide/sourceview/ide-word-completion-provider.h
@@ +24,3 @@
+G_BEGIN_DECLS
+
+#define IDE_TYPE_WORD_COMPLETION_PROVIDER            (ide_word_completion_provider_get_type ())

Convert to G_DECLARE_ stuff when you get a chance

@@ +47,3 @@
+
+GType                      ide_word_completion_provider_get_type (void) G_GNUC_CONST;
+IdeWordCompletionProvider *ide_word_completion_provider_new      (const gchar *name, GIcon *icon);

One parameter per line
Comment 20 Umang Jain 2017-08-31 01:14:08 UTC
Created attachment 358804 [details] [review]
Add word completion provider

Classes introduced:

IdeWordCompletionProvider
IdeWordCompletionItem
IdeWordCompletionResults
Comment 21 Umang Jain 2017-08-31 01:17:25 UTC
Created attachment 358805 [details] [review]
sourceview: Add begin-word-completion signal
Comment 22 Umang Jain 2017-08-31 01:18:04 UTC
Created attachment 358806 [details] [review]
Improve on how to replay results
Comment 23 Umang Jain 2017-08-31 01:18:30 UTC
Created attachment 358807 [details] [review]
word-completion-provider: Remove offset computation logic
Comment 24 Umang Jain 2017-08-31 08:39:24 UTC
Created attachment 358822 [details] [review]
Improve on how to replay results
Comment 25 Umang Jain 2017-08-31 08:40:47 UTC
Created attachment 358823 [details] [review]
word-completion-provider: Remove offset computation logic

Remove offset computation logic and order new buffer
scan if we detect completion is made on new lines.
Comment 26 Christian Hergert 2017-09-02 01:22:11 UTC
Congrats, it's in!

We should still work on polishing this up, but it's in good shape for merging.

In particular, we could *possibly* share the IdeWordCompletionProvider between
views (and just share it on the IdeBuffer).

Also, I'd like to see if we can tweak the sorting and placement of selected
item to more closely match vim. (I'm not sure if this will be possible yet, but
since we only have one provider visible, it might be). Basically, I want to see
the current word selected, with things before the word being above the
selection, and things after the word after the selection.

Attachment 358805 [details] pushed as 1805ed7 - sourceview: Add begin-word-completion signal
Attachment 358822 [details] pushed as b3f43fc - Improve on how to replay results
Attachment 358823 [details] pushed as 27a2d61 - word-completion-provider: Remove offset computation logic