These days we all enjoy the ease of use in graphical text editors. Some text editors will propose suggested words as your are typing. One thing that starts to move a generic text editor into more of a Integrated Development Environment (IDE) is the ability to get auto complete suggestions for common syntax and variable names. This simple little feature greatly improves the speed and ease of use in these environments.
I began to look around for guides on how to implement this in Python utilizing GtkSourceView. GtkSourceView is a powerful GNOME C library that many open source projects utilize as a text editor for multiple programming languages, including Gedit. It provides accurate syntax highlighting built-in along with several other features. As this is primary a C library with Python hooks provided by GObject Introspection, not all aspects of this library have examples or the best documentation for Python developers.
After much pain digging through C documentation and C examples, I put together a example of how to implement GtkSourceCompletion in Python. Before I get to all the code, let me walk through how GtkSourceCompletion works, and then we will get to how truly simple it is to implement.
First when you create a GtkSourceView there is an instance of a GtkSourceCompletion that is created with it. Even if you are not currently utilizing it, its living and breathing just waiting and wanting to give its user suggestions. To get a GtkSourceCompletion to start functioning you must set up a GtkSourceProvider and then add it to the main GtkSourceCompletion instance. When the text area is typed into, the completion will ask the providers if they have a possible match, then requests for those matches if applicable.
Now the fun part, you cannot create the provider directly. You can create it utilizing GtkSourceCompletionWords or create a custom provider. GtkSourceCompletionWords once defined will become a provider. Attach the provider to a GtkTextBuffer or GtkSourceView(it inherits a GtkTextBuffer). The Provider will pull from words in the buffer when requested for a possible match. The limitation to this is it is driven by Pango as Pango does not recognize special characters. This is the biggest limitation of the built in provider, but for a default word list or dictionary it is awesome! To implement this simple provider in your Python Gtk project use the following syntax in your project.
text_view = GtkSource.View()
text_buff = Gtk.Source.Buffer()
text_view.set_buffer(text_buff)
text_completion = text_view.get_completion()
view_provider = GtkSource.CompletionWords.new('main')
view_provider.register(text_buff)
text_completion.add_provider(view_provider)
The above block of code assumes you know how to attach a GtkSourceView to a window and utilize a Gtk Builder to initialize it. Once it is built and you start typing this provider will search for words through the words that you have already typed and if you start to reuse them present them as an option for auto complete.
Utilizing this same technique but not attaching the buffer to the GtkSourceView. You can manually set the text of the buffer to a list of words. This will provide a list of suggestions to the user for auto complete.
keyword_provider = GtkSource.CompletionWords.new('keywords')
keyword_buffer = Gtk.Source.Buffer()
keyword_buffer.set_text('GtkSourceView')
keyword_provider.register(keyword_buffer)
text_completion.add_provider(keyword_provider)
You can even add multiple providers to the GtkCompletion instance and thus allowing each provider to have its own source to pull words from. Just a note, if you have one attached to the main buffer and they complete a word from the second provider’s buffer it will show up in both suggestions as seen to the left unless you remove it and update the buffer accordingly. All in all, utilizing the built in hooks for this library is extremely simple to implement.
Lets get a little advanced now, lets build a custom provider, so we can make suggestions for completions based off of a regex and a left deliminator. For our custom provider we must have the following functions defined, as these will be called by the completion engine, and part of the GtkSourceProvider .
- do_get_name
- do_match
- do_populate
do_get_name returns the name of your provider. do_match returns either true or false, depending on if the provider has a valid match for the completion instance. In my case I always return true so I don’t have to fight with caching the search result as the completion instance will call do_populate. In do_populate here you want to search the text field of the GtkSource.View’s buffer provided from the GtkSourceCompletionContext that is sent to do_populate. One of the important notes here is the provide conducts matching and recommends words from what is based in the GtkTextBuffer instance. When we get to the custom provider, the matching will be conducted based off what the user has typed and found in the buffer but what is returned is what we tell it to. By utilizing a regex for what type of specific string you are looking for and setting up a left delimiter you can quickly see if you have a match, else return early. If you don’t bail out, provide your suggestion by adding the proposals to the context which will return it to the completion instance for display to the user. The custom provider example is a very rough example for Jinja context auto completion suggestions. In this case it will make recommendations for {{ custom.foo }} and {{ custom.bar }} and will trigger at {{ custom. For the full running example visit my py-GtkSourceCompletion-example on GitHub.
class CustomCompletionProvider(GObject.GObject, GtkSource.CompletionProvider):
"""
This is a custom Completion Provider
In this instance, it will do 2 things;
1) always provide Hello World! (Not ideal but an option so its in the example)
2) Utilizes the Gtk.TextIter from the TextBuffer to determine if there is a jinja
example of '{{ custom.' if so it will provide you with the options of foo and bar.
if select it will insert foo }} or bar }}, completing your syntax
"""
def do_get_name(self):
return 'Custom'
def do_match(self, context):
# this should evaluate the context to determine if this completion
# provider is applicable, for debugging always return True
return True
def do_populate(self, context):
proposals = []
# found difference in Gtk Versions
end_iter = context.get_iter()
if not isinstance(end_iter, Gtk.TextIter):
_, end_iter = context.get_iter()
if end_iter:
buf = end_iter.get_buffer()
mov_iter = end_iter.copy()
if mov_iter.backward_search('{{', Gtk.TextSearchFlags.VISIBLE_ONLY):
mov_iter, _ = mov_iter.backward_search('{{', Gtk.TextSearchFlags.VISIBLE_ONLY)
left_text = buf.get_text(mov_iter, end_iter, True)
else:
left_text = ''
if re.match(r'.*\{\{\s*custom\.$', left_text):
proposals.append(
GtkSource.CompletionItem(label='foo', text='foo }}')
)
proposals.append(
GtkSource.CompletionItem(label='bar', text='bar }}')
)
context.add_proposals(self, proposals, True)

