GNOME Builder: Improving word completion GSoC final report - I

- 3 mins

Hello Gnomers,

So, finally I got a chance to hook things into GNOME-Builder tree and change the CompletionProvider namespace from GtkSourceVimWordsCompletion to IdeWordCompletion*


If you want to just try out the feature; fetch wip/uajain/word-completion from Builder git repository and build it. Next step is to ensure that you are in “Vim - Insert Mode” in Builder(Preferences->Keyboard->Vim) and try Ctrl-n / Ctrl-p to word-complete.

Other key bindings (for Emacs/Gedit mode) will be added in future as we need to decide what those key bindings should be.

So, how things are structured ?

IdeWordCompletionProvider - The word completion provider responsible for creating word completion proposals and provide the proposals to GtkSourceCompletionContext

IdeWordCompletionItem - A single proposal; takes in offset (see gtk_text_iter_get_offset) as a parameter so that we can appropriately sort in completion window.

IdeWordCompletionResults - The result set containing all the potential proposals for the given match. IdeWordCompletionResults is reponsible for sorting the proposals based on direction; Ctrl-n(Forward Search), Ctrl-p(Backward Search). It is also reponsible for saving some extra buffer scans by replaying the result set, if possible. We will see this particular section in next post. For this post, I want to keep things simple.

By wiring the key bindings, we can set “direction” property of IdeWordCompletionProvider, which determines whether to sort the proposal in backward or forward order of matches. This property is carried forward to IdeWordCompletionResults in case we are able to replay the results without scanning again.

Wrapping head around GtkTextIter and GtkTextMark :

To extract a match while scanning the buffer, gtk_text_iter_get_text (GtkTextIter *match_start, GtkTextIter *match_end) is used. match_start and match_end comes from gtk_source_search_context_forward_finish2 or _backward_finish2

The buffer scans are asynchronous and GtkTextIter tend to go invalid on buffer changes. So, it’s quite possible that the buffer is changed (user types) while the async operating is still on. Therefore, match_start and match_end will be invalid and will provide a hefty warning on the terminal.

Gtk-WARNING **: Invalid text buffer iterator: either the iterator is uninitialized, or the characters/pixbufs/widgets in the buffer have been modified since the iterator was created.
You must use marks, character numbers, or line numbers to preserve a position across buffer modifications.
You can apply tags and insert marks without invalidating your iterators,
but any mutation that affects 'indexable' buffer contents (contents that can be referred to by character offset)
will invalidate all outstanding iterators

Therefore, in order to extract the match-text, GtkTextMark comes to rescue. Therefore, GtkTextMark preserve the location and can provide us with refreshed GtkTextIters which are not (yet) invalid. GtkTextIters at marks can be obtained by gtk_text_buffer_get_iter_at_mark.

Here is a short snippet of refresh_iters in IdeWordCompletionProvider.

static gboolean
refresh_iters (IdeWordCompletionProvider *self,
               GtkTextIter               *match_start,
               GtkTextIter               *match_end)
  IdeWordCompletionProviderPrivate *priv = ide_word_completion_provider_get_instance_private (self);
  GtkTextBuffer *buffer = NULL;

  g_assert (priv->start_mark != NULL);
  g_assert (priv->end_mark != NULL);

  buffer = gtk_text_mark_get_buffer (priv->start_mark);

  if (buffer)
      gtk_text_buffer_get_iter_at_mark (buffer, match_start, priv->start_mark);
      gtk_text_buffer_get_iter_at_mark (buffer, match_end, priv->end_mark);

      return TRUE;

  return FALSE;

Meanwhile getting the matched texts, IdeWordCompletionProvider also checks if we are not adding any duplicate proposals that probably have been added already. We weed out the duplicate proposal by keeping a hash table around.

Next, I would like to dig into how exactly IdeWordCompletionProvider scans and how we replay the result set to save some extra buffers scans and optimize.

A very short video of the implementation:

Umang Jain

Umang Jain

Makes the machines talk

comments powered by Disqus
rss facebook twitter github youtube mail spotify instagram linkedin google pinterest medium