What can you extend in Builder?

Erick asked recently about what you can extend in Builder. I figured that would be better as a blog post, so hopefully he doesn’t mind the public attention!

Of course, everything in Builder is under development, and there are lots of things that are not yet ready for prime time as plugins. But we are getting there pretty quickly.

Also note, that this list is based on 3.19.90, which was just released last week. It looks a little something like this.

Screenshot of Builder 3.19.90 in with Dark Mode theme

Completion Providers

IdeCompletionProvider, which is really just an extension of GtkSourceCompletionProvider allows integration with auto-completion in Builder. There are a bunch of examples in-tree. Clang is rather complex, but there is also Python Jedi, and Ctags.

Application Addins

As discussed previously, IdeApplicationAddin can be thought of singletons within the application. A nice way to share information in your plugin that is not project specific.

Application Tools

An IdeApplicationTool allows creating a sub-command that is accessible using the ide command. Compare this to how you use the git command line tools.

Services

An IdeService is similar to an application addin, but is per-project. So each loaded project in Builder will have the service instantiated. We use this in the clang plugin to manage background AST generation. We then use these ASTs to do things like auto-completion, symbol resolving and symbol tree, semantic highlighting, and more.

Build Result Addins

When performing builds with Builder, you might want to extract information from the build output. This is exactly what the GCC plugin does to extract build time diagnostics. Your extension will be loaded when building and can receive callbacks or signals during the build process. See IdeBuildResultAddin.

Build Systems

The Build System support in Builder is provided by plugins. Currently, we only support autotools out of the box. However, I’m looking forward to contributions for more build systems. To implement a build system, you’ll need to take a look at IdeBuildSystem, IdeBuildResult, and IdeBuilder.

Devices

It’s no secret that my goal for Builder is to raise the bar in terms of developing with external devices. We don’t have much using this yet, but the goal long term is to listen to udev events and show devices as they are added in Builder. See IdeDeviceProvider for adding support here. Long term, this will be integral when interacting with IdeRuntime so that we can setup cross-compilers.

You can even imagine the devices being remote and connected via SSH.

Semantic Highlighting

Builder has support to make writing semantic highlighters easier. One of the tough parts of writing a highlighter is doing so at such a rate that does not block the main loop or make the UI stutter. IdeHighlighter is how we do this. Some examples are our clang highlighter and xml highlighter.

Auto Indenters

While I have grand ideas for new ways to write auto-indenters, our interface IdeIndenter will probably not need to change. This type of thing is always a lot of spaghetti code, rife with backtracking. But in 3.22 I hope to simplify this with a new “selector-based” indentation engine. More on that in the coming months. Currently we have a basic GNU C89 indenter, Python, XML, and Vala.

Perspectives

It’s no secret that I’m not happy with the current sidebar in Builder, but it’s the first step in the direction we want to go. Gotta start somewhere! But the piece that is very useful, is the concept of perspectives. Long term I expect perspectives to be how we avoid the the “jam everything into toolbars” IDE trap. Simply add the IdePerspective to the IdeWorkbench to add a new perspective. We current have 3, the editor, build configurations, and preferences. I expect a couple more by 3.22.

Workbench Addins

The IdeWorkbenchAddin allows you to tweak the UI of the workbench. Your addin is created once the workbench has loaded a project. You might add a GAction to be used in your UI, add a perspective, or surprise me.

Preferences

The IdePreferencesAddin is how you add new preferences to Builder. It has a declarative interface to add switches and knobs and be able to remove them when your plugin unloads. This feature is how we avoid the “Configure Plugin” trap I complain so much about in “plugin orientated architectures”.

Project Miners

Unfortunately, last I tried tracker to do project discovery I ended up with about 20 GB of resident memory and then my system crashed. So we currently do project mining manually from Builder. I really hope this isn’t always the case, but it’s where we are today.

Use IdeProjectMiner to implement a project miner and it will get shown when Builder shows the project greeter. We currently only discover autotools projects on your system.

System Runtimes

An IdeRuntime is how we manage executing processes within a context such as a jail, container, or host system. Think of it as a toolchain that can be separate from your host system and used for compilation and execution tasks.

The IdeRuntimeProvider interface is how you discover and create these runtimes.

Currently, we have two (well three) implementations. One for Xdg-App runtimes, and one for Jhbuild runtimes. The third is the host system, but that is more of a “no-op” implementation.

I can see more of these being added in the future. Python’s virtualenv, multiple ruby installations, Mono version runtimes, Docker, etc.

When integrating these with an IdeDevice, I hope that we can even get off-system building working inside of virtual-machines (Boxes for example). For me, that would simplify the “Build on Fedora”, “Deploy on RHEL” story.

Search Providers

The search box at the top of Builder is powered by IdeSearchProviders. I really hope to make this feature more powerful in upcoming releases.

Symbol Resolvers

The IdeSymbolResolver interface allows you to locate a symbol given a position in a text buffer. This is how we jump to source locations when you type <alt>. in a buffer (or gd in vim mode).

The symbol resolver also helps us create a hierarchy of symbols as found in the Symbol Tree panel.

See the clang and vala implementations.

Version Control

Version control in Builder is implemented using the IdeVcs interface. Currently, we only support git, thanks to the wonderful libgit2-glib library.

If the version control interface implements the IdeVcs::get_buffer_change_monitor() vfunc, then you will magically get those pretty change lines in the text editor gutter.

Editor Addins

If you want to add extensions to every editor created in Builder, use the IdeEditorViewAddin interface.

This interface is rather incomplete today, so I’m really interested in the things you’d like to do so we can make this as easy as possible.

Project Creation

The project greeter, as seen when Builder launches, has support to create new projects. Although, today we only support cloning from Git or from a local directory. In 3.22 we need to extend this to support our new project template support.

To add a new project creation method, implement the IdeGenesisAddin. You can find the git and directory implementations for inspiration.

Project Templates

The basic plumbing for project templates has landed, but it is only exposed via the ide create-project command line program for 3.20. As I said above, I hope to complete this for 3.22 (or mentor someone to complete it for me, hint hint).

Implement the IdeTemplateProvider and IdeProjectTemplate interfaces for how to go about doing this.

The autotools+shared-library, which is written in Python, might serve as some inspiration.

Multi-process Coordination

To keep the Builder process lightweight, we advise doing memory and CPU intensive operations in sub-processes. The IdeWorker interface makes this easy. Builder will spawn a worker process for you, and setup a private D-Bus (no daemon required) to communicate to your sub-process.

Longer term, we hope to add more containment features, CPU and memory throttling (with cgroups), and process recycling when memory fragmentation gets absurd.

This would be a great GSoC project if any students are still reading this far down ;)

Conclusion

We have a lot more things to add in the near future. See my work-in-progress ideas for 3.22 features on our Roadmap.

Build Configurations and Xdg-App

It’s no secret that one of the main features I wanted to land this cycle was introductory support for Xdg-App. There really was quite a bit to do to make that happen, including all sorts of seemingly unrelated plumbing.

One seemingly unrelated piece is the long-anticipated support for “Build Configurations”. I deferred on this feature for as long as possible because it needs to support many movable parts, for which we didn’t have a clear vision of. But now that our applications vision is becoming less and less murky, it was time to attack it.

IdeContext now contains an IdeConfigurationManager. This object can be used to get access to available configurations (IdeConfiguration). A configuration currently contains a target device (IdeDevice), a target runtime (IdeRuntime), environment variables (IdeEnvironment), and various other common properties.

We’ve ambitiously tried to avoid writing random dot files to your project, but time has come to add one. If you create a build configuration, we’ll drop a small .buildconfig GKeyFile in the root of your project tree. It contains the necessary information to bootstrap your project or build it with a given Xdg-App runtime.

Currently, we only have two runtime implementations. The default, is simply the host system. Think of it as a pass-through runtime. The second, is an Xdg-App runtime. It uses the runtime SDK installed via the xdg-app command line tool. For 3.22, I expect to have UI to install runtime SDKs.

You might imagine additional runtimes that we could create. Jhbuild is an obvious one, although hopefully it is less and less necessary now that we have a nightly GNOME SDK.

Build Configuration

EggColumnLayout

The widget behind the new preferences implementation in Builder was pretty fun to write. Some of the details were tricky, so I thought I’d make the widget reusable in case others would like to use it. I’m sure you can find uses for something like this.

The widget allocates space for children based on their priority. However, instead of simply growing in one direction, we allocate vertically, but spill over into an adjacent column as necessary. This gives the feeling of something akin to GtkFloxBox, but without the rigidity of aligned rows and columns.

This widget is meant to be used within a GtkScrolledWindow (well GtkViewport really) where the GtkScrolledWindow:hscrollbar-policy is set to GTK_POLICY_NEVER.

The resulting work is called EggColumnLayout and you can find it with all the other Egg hacks.

EggColumnLayout in Builder preferences

A short description of the tune-ables are:

EggColumnLayout:column-width tunes the width of the columns. These are uniform among all columns so you should set it to something reasonable. The default is 500px. As an aside, dynamically descovering the column width uniform to all children would probably not look great, nor be straight-forward.

EggColumnLayout:column-spacing is the spacing between columns. Had we used children containers, we could have probably done this in CSS, but the property is “good enough” in my opinion.

EggColumnLayout:row-spacing is the analog to :column-spacing but for the space between the bottom of one child and the top of a subsequent child.

That is pretty much it. Just add your children to this and put the whole thing in a GtkScrolledWindow.

Happy Hacking.

Project Templates

Now that Builder has integrated Template-GLib, I started working on creating projects from templates. Today, this is only supported from the command line. Obviously the goal is to have it in the UI side-by-side with other project creation methods.

I’ve put together a demo for creating a new shared-library project with autotools. Once I’m happy with the design, I’ll document project templates so others can easily contribute new templates.

Anyway, you can give it a go as follows.

ide create-project my-project -t shared-library
cd my-project
ide build

Hiding, Dock-able, and Floating Panels

I’m doing some research on panel systems that people love, hate, and love to hate. Send me a short email with the following to get your voice heard. No promises of implementation, but I’d like to inform myself nonetheless.

Name: Application or Library Name
Screenshots: 1-2 screenshot links
Likes: 1-2 Sentence max of likes
Dislikes: 1-2 Sentence max of dislikes

Builder Plugins – Part II

Previously, Builder Plugins, Part I. Read this if you haven’t.

Now that we’ve learned the mechanics of creating a plugin, let’s look at another plugin interface, IdeWorkbenchAddin. This interface allows us to extend the IdeWorkbench. An IdeWorkbench is the toplevel window for a given project. Since Builder supports having more than one project open at a time (each in a given workbench window), this is the interface you would use for extensions that are per-project.

the workbench window

The workbench window has an important property. The IdeWorkbench:context. This is the IdeContext. Each loaded project has an IdeContext. This is the primary data structure that provides access to your Version Control System, Build System, Device Manager, and many other components.

The two important virtual functions in the IdeWorkbenchAddin interface are IdeWorkbenchAddin::load and IdeWorkbenchAddin::unload. These provide an IdeWorkbench as a parameter, for which you can use to your hearts content. IdeWorkbenchAddin::unload() should reverse anything that happened in IdeWorkbenchAddin::load().

The other virtual functions are optional, and can be used to allow your IdeWorkbenchAddin to perform custom actions upon opening URIs and such. I’m sure we’ll add more as we go.

Anyway, let’s create a sample IdeWorkbenchAddin that prints some information about our loaded project.

# my_plugin.py

from gi.repository import Ide
from gi.repository import GObject

class MyWorkbenchAddin(GObject.Object, Ide.WorkbenchAddin):
    def do_load(self, workbench):
        self.workbench = workbench

        context = workbench.props.context
        vcs = context.props.vcs
        build_system = context.props.build_system

        print("Version Control System is:", repr(vcs))
        print("Build System is:", repr(build_system))

    def do_unload(self, workbench):
        self.workbench = None

Use the same my_plugin.plugin found in Part I.

Running Builder should result in something like this on the command line.

Version Control System is: <__gi__.IdeGitVcs object at 0x7fffbd8de438 (IdeGitVcs at 0x1caaee0)>
Build System is: <__gi__.IdeAutotoolsBuildSystem object at 0x7fffbd8de798 (IdeAutotoolsBuildSystem at 0x1d00890)>

You can see that my project uses Git for Version Control, and Autotools for the build system. Nothing too spectacular here, so now we’ll move on to something a bit more complex.

perspectives See each of the icons on the left of the workbench? Those are what we call perspectives. Currently, we only have two perspectives. One for the editor, and one for preferences. I’m sure you can imagine a bunch of new perspectives. Bugzilla, git, debugger, profiler, database browser, and designer all come to mind.

I should mention that I’m not sure whether or not we’ll keep the perspective bar like this. It’s been there since some of the earliest designs, but we may opt for something slightly different.

Like many of the other extension points in Builder, IdePerspective is an interface you can implement in your library. However, you need to add it to the workbench using IdeWorkbench.add_perspective(). As always, you should remove it when you unload your plugin, using IdeWorkbench.remove_perspective().

Lets create a Hello World perspective.

# my_plugin.py

from gi.repository import Ide
from gi.repository import GObject
from gi.repository import Gtk

class MyWorkbenchAddin(GObject.Object, Ide.WorkbenchAddin):
    def do_load(self, workbench):
        self.perspective = HelloPerspective(visible=True)
        workbench.add_perspective(self.perspective)

    def do_unload(self, workbench):
        workbench.remove_perspective(self.perspective)
        self.perspective = None

class HelloPerspective(Gtk.Box, Ide.Perspective):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        label = Gtk.Label(label="Hello, World!",
                          expand=True,
                          visible=True)
        self.add(label)

        self.titlebar = Ide.WorkbenchHeaderBar(visible=True)

    def do_get_id(self):
        return 'hello-world'

    def do_get_title(self):
        return 'Hello'

    def do_get_priority(self):
        return 10000

    def do_get_icon_name(self):
        return 'image-missing'

    def do_get_titlebar(self):
        return self.titlebar

Lets run Builder and see what happens!

Screenshot from 2016-01-21 15-48-43

More next time, same bat channel.

Builder Plugins – Part I

The time to start writing plugins for Builder is here!

While the API of LibIDE may churn a bit, now is a good time to get started on your plugin of choice. Things are easier than ever and we hope to continue trending in that direction.

We support writing plugins in a variety of languages. Currently, C, Vala, and Python 3 are all supported in Builder.You can find examples of all of these in the plugins/ directory.

We use libpeas for our plugin support. That means the process for creating a plugin in Builder is quite similar to Gedit, Rythmbox, Eog, and others.

I’m going to use Python 3 as our language of choice for examples due to the succinctness.

Create a .plugin description

The first step in our plugin is to create a plugin description file. The convention we use for this is module-name.plugin.

[Plugin]
Name=Hello World
Description=A sample hello world plugin
Authors=Example 
Copyright=Copyright © 2016 Example

Loader=python3
Module=my_plugin

The stuff at the top is your basic title, description, and attribution.

The two lines at the bottom tell us what plugin loader to use, and the name of the module to load. In this case, we want to use the python3 loader. Omitting this line would default to the C loader, which can load a shared library using an equivalent to dlopen().

The Module, in this case, is our Python 3 module. If we were doing this in C or Vala, it would be the name of the shared library, omitting the “lib” prefix and “.so” suffix.

Create a plugin Python module

Lets create our python module now. The following will create an IdeApplicationAddin. An IdeApplicationAddin allows you to extend the IdeApplication object. There is only ever one IdeApplication per-process, so this is an excellent place to put “singleton” type code.

# my_plugin.py

from gi.repository import GObject
from gi.repository import Ide

# Ide.ApplicationAddin interface requires a GObject, so we
# inherit from GObject.Object. Ide.ApplicationAddin is an
# interface and it has two override'able methods. do_load()
# and do_unload(). Each take the application instance as a
# parameter.
class MyAppAddin(GObject.Object, Ide.ApplicationAddin):
    def do_load(self, app):
        print("Hello, World!")

        # If you'd like to access this instance like a
        # singleton from other extensions in your plugin,
        # then you might want to give yourself access to
        # the MyAppAddin instance.
        #
        # Doing the following will allow you to use
        # "MyAppAddin.instance" as your singleton.
        MyAppAddin.instance = self

    def do_unload(self, app):
        # Unload our singleton
        MyAppAddin.instance = None

That’s it! Put my_plugin.plugin and my_plugin.py in ~/.local/share/gnome-builder/plugins and restart Builder.

You should see your plugin in the Extensions section of the Preferences perspective.

hello-world

You can imagine lots of things you might want to do only once per-process. Setup a DBus service, manage compiler tooling, provide access to an external web service, you name it.

In my next post, we’ll take things a bit deeper now that we have the machanics of creating a new plugin.

EggStateMachine

So the topic of the day is EggStateMachine. This is a handy class for managing various states in your UI that might have complex transitions. One example might be the GNOME 3 style search pattern. The search pattern has a few states.

  • Inital state which contains the content overview
  • The active search state, including type-ahead search
  • Object selection state
  • Object selection state with a selection

When we switch between the above states, we might need to:

  • Toggle search and object-selection toggle buttons
  • Toggle search revealer visibility
  • Toggle action bar visibility
  • Toggle action bar button sensitivity
  • Show checkbuttons on content overview
  • Alter headerbar CSS classes
  • Toggle visibility of close button on header bar
  • Toggle visibility of cancel button on header bar

You get the idea. Getting everything right is critical to the UX, and that becomes unpleasant code rather quickly.

EggStateMachine allows you to do much of above all from your Gtk+ .ui template file. Additionally, you can use the native C API if using .ui templates is not your thing. For those people, you can jump to the header file to get the idea, link at the bottom.

For the rest of you, take a look at this snippet.

<object class="EggStateMachine">
 <property name="state">browse</property>
 <states>

  <state name="browse">
   <object id="titlebar">
    <property name="show-close-button">true</property>
   </object>
   <object id="cancel_button">
    <property name="visible">false</property>
   </object>
   <object id="action_bar">
    <property name="visible">false</property>
   </object>
  </state>

  <state name="selection">
   <object id="titlebar">
    <property name="show-close-button">false</property>
    <style>
     <class name="selection-mode"/>
    </style>
   </object>
  </state>

 </states>
</object>

This is just a snippet, and shows two states. The “browse” state and the “selection” state. You can see that you can reference other objects in your .ui definition using the <object id=""> syntax. While I didn’t show it here, you can use the binding syntax to bind properties only in a given state.

You can also define style-classes in the state. When the state transitions, the style-class will automatically be added or removed.

To perform a state transition, set the EggStateMachine:state property. You can also create an action for this using egg_state_machine_create_action(). If you add that action to your widget hierarchy (such as win.state), then you can remove the need to perform complex toggles in your application code as well. Take a look at the following snippet.

<object class="GtkToggleButton" id="selection_button">
  <property name="action-name">win.state</property>
  <property name="action-target">'selection'</property>
  <child>
   <object class="GtkImage">
    <property name="visible">true</property>
    <property name="icon-name">object-select-symbolic</property>
   </object>
  </child>
</object>

To add the state action to your widget hierarchy, do something like:

GAction *action;

action = egg_state_machine_create_action (state_machine, "state");
g_action_map_add_action (G_ACTION_MAP (my_window), action);
g_object_unref (action);

Now the button will be toggled when the state matches "selection".

And for practical use, see Builder’s greeter implementation.

EggAnimation

Here is a nifty trick I’ve had up my sleeve since the Gtk 2.x days (wow, over half a decade ago!). I’ve used this on a bunch of projects over the years, and it always gets a few smiles. It has a very similar API to the early days of Clutter, where it’s inpsiration came from. I should preface this with: If you need very fast, GPU optimized graphics, this is not what you want. However, for automating property changes, it is very handy.

Let’s start simple by transitioning the opacity of a widget from 0 (transparent) to 1 (opaque).

gtk_widget_set_opacity (widget, 0.0);
egg_object_animate (widget,
                    EGG_ANIMATION_LINEAR,
                    1000, /* duration in msec */
                    NULL, /* frame clock, NULL for default */
                    "opacity", 1.0, /* key/value properties */
                    NULL);

This is fairly contrived, because you almost certainly want to do the above with CSS transitions.

Now consider scrolling to the bottom of a GtkScrolledWindow. The GtkAdjustment is not a widget, and therefore does not have a frame-clock. So we will pass the frame-clock of the scrolled window instead.

GdkFrameClock *frame_clock;
GtkAdjustment *v_adjustment;
gdouble upper;
gdouble page_size;
gdouble value;

frame_clock = gtk_widget_get_frame_clock (scroller);
v_adjustment = gtk_scrollable_get_vadjustment (scroller);
upper = gtk_adjustment_get_upper (v_adjustment);
page_size = gtk_adjustment_get_page_size (v_adjustment);
value = MAX (0.0, (upper - page_size));

egg_object_animate (v_adjustment,
                    EGG_ANIMATION_EASE_IN_OUT_QUAD,
                    1000,
                    frame_clock,
                    "value", value,
                    NULL);

egg_object_animate() returns a weak pointer to the EggAnimation. This is safe since the animation won’t start until the next iteration of the main loop. If you want to hold on to it, either use g_object_weak_ref() or g_object_ref. I prefer the weak ref (via g_object_add_weak_pointer()) so it is easy for me to cancel in-flight animations with egg_animation_stop().

Here is an example of how we use EggAnimation in Builder to make the “yank” operation in Vim mode more visible.

For many things, you’ll be able to just use CSS transitions. And you should definitely check if that is an option before using EggAnimation.

But that said, it’s used all over in Builder. If I did my job well, you barely even notice. And of course, for the animation hating crowed, GtkSettings:gtk-enable-animations is respected.