Text Editor has really been shaping up in the past couple weeks as we race towards getting things ready for GNOME 42.
We removed the preferences sidebar experiment because it was a bit clunky and none of the other core apps shared the design metaphor. Instead we’ve brought back a preferences dialog, albeit with an improved design. It builds on the previous GtkSourceStyleSchemePreview work but with a filtered set based on the current light/dark desktop setting.
The “Open” popover also got another round of design work based on using it for a while. The style is a bit slimmer and more to the point. It is also much more keyboard navigable now. It’s nice to be able to hit Ctrl+K, type a few characters, and either hit Return to open the first match or navigate through the list with arrows. Escape will return you back to the editor. Easy stuff.
I added a GSetting for those that rely on visual spaces. Even though there is no UI for this at the moment you can still get what you want fairly easily using the gsettings command line tool.
flatpak run --command=gsettings org.gnome.TextEditor.Devel \
set org.gnome.TextEditor draw-spaces \
If you’re feeling experimental you can even try my easter egg to test out the Vim emulation using GtkSourceVimIMContext. It probably will never be an advertised feature, but it’s there.
flatpak run --command=gsettings org.gnome.TextEditor.Devel \
set org.gnome.TextEditor keybindings vim # or "default"
The most visual piece of work this week was in introducing recoloring support. It builds atop libadwaita and uses a CSS provider to override the colors in the theme. I expect there will be a recoloring API in the not-too distant future for libadwaita which will provide this for us.
When you select a style-scheme Text Editor will now use the colors defined in the scheme to alter how the entire application looks. For example, below are screenshots for a number of style-schemes both bundled with GtkSourceView and found on the internet.
And yes, the coloring is reflected throughout the entire interface.
One of the things we spent some time on early with Builder was creating built-in VIM emulation. Originally it was written in C, but then got converted to this nasty CSS for a couple of reasons. First, that keybindings in GTK 3 were largely done with CSS due to how we moved away from gtkrc in the 2.x to 3.x migration. And beyond that, CSS was the one way that’d let us share a lot of keybinding code between shortcut themes so we could support emacs, vim, sublime, etc.
One thing that didn’t work well was VIM’s “modes”. People often think of VIM as a single state (normal, insert, replace, visual, etc). In reality it’s a stack of them. Just look at the following state diagram if you’re going to actually to match things somewhat accurately. Doing this in a manner that would allow sharing between shortcut themes in Builder was simply beyond the amount of time we had to spend on it given all the other features that needed to land.
As we start to port Builder to GTK 4 this is one area that worried me the most. GTK 4 has improved so much in this regard, but we also have this giant legacy ball of tar. So recently I started looking at a new approach to providing VIM emulation in a way that would let us improve the quality and extend it’s reach.
The experiment I’ve come up with so far is to build upon GtkIMContext. This allows us to filter key presses and releases from the widget and do so in a way that allows layered input-methods (ibus, emoji, unicode, etc) to continue working.
One question that will always pop up is “why not use $some_form_of_vim” and bridge that. The answer is pretty simple, it’s a huge dependency to take on as a library. It also requires synchronizing text and across boundaries which may not even be capable in places where GtkSourceView is used. Beyond that there are a large number of impedance mismatches in how things work from a generic standpoint.
But fear not, we can actually do pretty good for the most commonly used things. We can probably even implement registers (some of it is there already) and marks for people that want it. Some day I imagine we’ll land code folding upstream, so even supporting that might be possible.
My goal is to get this into 5.4 so that we have it well tested by time Builder lands a GTK 4 port. The exposed API is very small and just the single object GtkSourceVimIMContext. By keeping the implementation private we allow ourselves quite a bit of flexibility to adapt how we implement things if and when that is necessary.
I haven’t decided yet if we would want to have a property like GtkSourceView:enable-vim to simply enable it. I’m hesitant to do that because supporting it correctly can still be somewhat involved. You need presentation of a command bar and active command state for example. To do that requires application coordination anyway, so perhaps requiring the application to add a GtkEventControllerKey with the GtkSourceVimIMContext is the better option.
If you’d like to help on any of this, feel free to send me MRs. Everyone has slightly different usage of VIM and there are lots of missing spots you can fill in.
git clone https://gitlab.gnome.org/GNOME/gtksourceview.git
git checkout wip/chergert/vim
hack and repeat
Builder needs to deal with many SDK and SDK extensions for applications built upon Flatpak.
One thing I never liked about how we did this up until now was that we needed to install Flatpak remotes into the user’s personal Flatpak installation. First, because we needed to add Flathub and gnome-nightly repositories. Secondly, once a year we need to add the flathub-beta remote due to post-branch SDKs relying on beta extensions.
Previously this would pollute things like GNOME Software with versions of applications that you might not care about as a user.
In Builder 41, a private Flatpak installation is used in $XDG_DATA_DIRS which contains those remotes. Additionally we set a filter to only allow runtimes and specifically ones matching certain globs.
I realize I don’t blog much these days, but I do try to keep my Twitter filled with screenshots as I work on GNOME.
Recently I spent some time doing another round of performance improvements in GtkSourceView.
Much work this cycle has focused on submitting work to the GPU more efficiently. For example, Matthias Clasen taught the new OpenGL renderer to submit colors along with glyph vertices so that it could have fewer GL uniform updates along with fewer program switches. This has had the effect of letting us batch common GtkTextView usage into a single glDrawArrays() submission. Great stuff!
I’ve been striving to reach 144hz text scrolling ever since a kind GNOME contributor sent me a 144hz monitor to test with. So with the new bits in place, I took another look at what was slowing us down.
Line Number Drawing
Line number drawing was still pretty high on the list. We already did some performance work there to avoid generating line number strings using a technique that caches a bit of information to tweak a single character in a static buffer. This time, however, the slowdown is in measuring text in Pango.
A quick dive into the code reveals that we have to measure the text to right-align line numbers. Since this is expected to be monospace we can cache common text widths and only measure once in a while. Simple and done.
Right Margin Drawing
Next up was a bit more trickier, in that it involved a lucky guess on my part. Taking a quick look at GtkSourceView’s snapshot_layer() implementation I noticed that the right margin is drawn above the text layer. That ultimately means that we must be doing an alpha composite when drawing. That both perturbs the quality of text beneath it and is more complex to draw.
When possible, the new code pre-calculates the blend between the background and the right-margin color to get the would-be composited color. That allows us to draw it beneath the text without an alpha channel and avoid the fairly large alpha composite altogether.
I also spent some time cleaning up how event handling works with GtkSourceMap (our minimap). It should feel quite a bit smoother and natural now. Thankfully this is a lot easier in GTK 4 than it was previously.
In GTK 4, I added new API to get access to the PangoContext so that we could control glyph alignment in GtksourceView. This makes our “Block” font in the GtkSourceMap look a bit better now.
To keep the GtkourceMap from being too distracting, we also tweak the default foreground color a bit so that it has less oomph than your actual GtkSourceView.
If you set a left-margin on a GtkSourceMap it will also now try to center your map which makes it easier for applications to provide some padding for improved aesthetics.
Now that Adwaita uses transparency for selections, the Adwaita style scheme needed to copy that.
Most of my career I’ve been working on a text editor product in either a hobby or professional capacity. Years ago I had an idea to combine a B+Tree with a PieceTable and put together a quick prototype. However, it didn’t do the nasty part which was removal and compaction of the B+Tree (so just another unfinished side-project).
Now that we’re between GNOME cycles, I had the chance to catch up on that data structure and finish it off.
Just for a bit of background, a B+Tree is a B-Tree (N-ary tree) where you link the leaves (and often the branches) in a doubly-linked list from left-to-right. This is handy when you need to do in-order/reverse-order table-scans as you don’t need to traverse the internal nodes of the tree. Unsurprisingly, editors do this a lot. Since B+Trees only grow from the root, maintaining these linked-lists is pretty easy.
And a PieceTable is essentially an array of tuples where each tuple tells you the length of a run of text, what buffer it came from (read-only original buffer or append-only change buffer), and the offset in that buffer. They are very handy but can become expensive to manage as you gain a lot of changes over time. They are fantastic when you want to support infinite undo as you can keep an append-only file for individual changes along with one for the transaction log. You can use that for crash recovery as well.
This augmented B+Tree works by storing pointers to children branch-or-leaves along with their combined run-length. This is handy because as you mutate runs in the leaves, you only need to adjust lengths as you traverse back up the tree.
Another bit of fun trickery when writing B-trees of various forms is to break your branches-or-leaves into two sections. One section is for the items to be inserted. On the other, you have a fixed array of integers. After you insert an item into a free slot, you update a linked list of integers (as opposed to pointers) on the other end. Doing so allows you to do most inserts/removals in O(1) once you know the proper slot and avoid a whole series of memmove()s. Scanning requires traversing the integer linked-list instead of your typical for (i=0; i<n_items; i++) scenario. Easily resolved with a FOREACH macro.
Anyway, here it is, and it seems to work. Finally I can move on from having that bit of data-structure on my mind.
One of the last features from Builder I really wanted to get upstream for the 5.0 release (and the beginning of a new ABI stream) is our auto-indenter interface. It, however, was a product of the times in GTK 3 and was rather clunky by necessity.
Now that we are on GTK 4, I could significantly clean up the implementation by putting it in GtkSourceView directly.
The interface is rather simple, and focused purely on indentation as you type. Note that we may add additional functions or interfaces in the future for reformatting text similar to what LSPs can do on save.
Thanks to a bit of GtkIMContext trickery we can avoid making the indenters have to translate keyvals like GDK_KEY_Return into strings to be inserted into the buffer. That was always a complex bit of code when input methods and alternate keymappings are in play.
So this is about as minimal of an interface as I could get, and that is generally what I strive for.
I spent a lot of time in 2020 working on projects tangential to what I’d consider my “main” projects. GtkSourceView got a port to GTK 4 and a load of new features, GTK 4 got a new macOS backend, and in December I started putting together a revamp of GTK 4’s GL renderer.
The nice thing about having multiple renderer backends in GTK 4 is that we still have Cairo rendering as an option. So while doing bring-up of the new GTK macOS backend I could just use that. Making software rendering fast enough to not be annoying is a good first step because it forces you to shake out performance issues pretty early.
But once that is working, the next step is to address how well the other backends can work there. We had two other backends. OpenGL (requiring 3.2 Core and up) and Vulkan. Right now, the OpenGL renderer is the best supported renderer for acceleration in terms of low bug count, so that seemed like the right way to go if you want to stay inline with Linux and Windows backends. Especially after you actually try to use MoltenVK on macOS and realize it’s a giant maze. The more work we can share across platforms (even if temporarily) the better we can make our Linux experience. Personally, that is something I care about.
From what I’ve seen, it looks like OpenGL on the M1 was built on top of Metal, so it seems fine to have chosen that route for now. People seem to think that OpenGL is going to magically go away just because Apple says they’ll remove it. First off, if they did, we’d just fallback to another renderer. Second, it’s likely that Zink will be a viable (and well funded) alternative soon. Third, they just released a brand new hardware architecture and it still works. That was the best point in time to drop it if there ever was one.
The NGL renderer makes full snapshots of uniforms and attachments while processing render nodes so that we can reorder batches going forward. Currently, we only reorder by render target, but that alone is a useful thing. We can start to do a lot more in the future as we have time. That might include tiling, executing batches on threads, and reordering batches within render targets based on programs so long as vertices do not overlap.
But anyway, my real motivation for cleaning up the GL renderer was so that someone who is interested in Metal can use it as a template for writing a renderer. Maybe that’s you?
Major shout-out to everyone that worked on the previous GL renderer in GTK. I learned so much from it and it’s really quite amazing to see GTK 4 ship with such interesting designs.
It can be very handy to store things you might do as meta programming in your GObjectClass‘s private data (See G_TYPE_CLASS_GET_PRIVATE()).
Doing so is perfectly fine, but you need to be aware of how GTypeInstance initialization works. Each of your parent classes instance init functions are called before your subclasses instance init (and in order of the type hierarchy). What might seem non-obvious though is that the GTypeInstance.g_class pointer is updated as each successive _init() function is called.
That means if you have my_widget_init() and your parent class is GtkWidget, the gtk_widget_init() does not know it’s instantiating a subclass. Further more, GTK_WIDGET_GET_CLASS() called from gtk_widget_init() will get you the base classes GtkWidgetClass, not the subclasses GtkWidgetClass.
There are ways around this if you don’t use G_DEFINE_TYPE(), but honestly, who wants to do that.
One technique around this, which I used in Bonsai’s DAO, is to use a single-linked list where the head is in each subclass, but the tail exists in each of the parent classes. That way you share all the parent structures, but the subclasses can access all of theirs. You’ll still want to defer most setup work until constructed() though so you can get the full class information of the subclass and hierarchy.
First off, before using Sysprof to improve the performance of a particular piece of software, make sure you’re compiling with flags that allow us to have enough information to unwind stack frames. Sysprof will use libunwind in some cases, but a majority of our stack unwinding is done by the Linux kernel which can currently only follow eh_frame (exception handling) information.
I generally disable the G_SLICE allocator because it isn’t really all that helpful on modern Linux systems using glibc and can also make it more difficult to track down leaks. Furthermore, it can get in the way of releasing memory back to the system in the form of malloc_trim() should we start doing that in the future. (Hint, I’d like to).
Finding code run often on the system
Sysprof, at it’s core, is a “whole system” profiler. That means it is not designed to profile just your single program, but instead all the processes on the system. This is very useful in a desktop scenario where we have lots of interconnected components.
At this point, excercise your system to try to bring out the behavior you want to optimize. Then click “Stop” to stop recording and view the results.
You’ll notice a lot of time in gnome-software there. It turns out I’m on a F32 alpha install and there was a behavior change in libcurl that has screwed up a number of previously valid use cases. But if I didn’t know that already, this would point me where to start looking. You’ll notice that I hadn’t compiled libcurl or gnome-software from source, so the stack traces are not as detailed as they would be otherwise.
On the right side is a callgraph starting from “[Everything]”. It is split out by process and then by the callstack you see in that program. On the top-left side, is a list of all functions that were collected (and decoded). On the bottom-left side is a list of callers for the selected function above it. This is useful when you want to backtrack to all the places a function was called. (Note that this is a sampling-based profiler, so there is no guarantee all functions were intercepted).
Use this information to find the relevant code within a particular project. Tweak some things, try again, test…
Tracking down extraneous allocations
One of the things that can slow down your application is doing memory allocations in the hot paths. Allocating memory is still pretty expensive compared to all of the other things your application could be doing.
In 3.36, Sysprof gained support for tracking memory allocations with a LD_PRELOAD. However, it must spawn the application directly.
At this point run your application to exercise the targeted behavior. Then press “Stop” and you’ll be presented with the recording. Usually the normal callgraph is selected by default. Select the “Memory Allocations” row and you’ll see the memory callgraph.
This time you’ll see memory allocation size next to the function. Explore a bit, and look for things that seem out of place. In the following image, I notice a lot of transforms being allocated. After a quick discussion with Benjamin, he landed a small patch to make those go away. So sometimes you don’t even have to write code yourself!
A variant of this patch went into Mutter’s copy of Clutter for a healthy memory improvement too.
Finding main loop slow downs
In Sysprof master, we have a “Speedtrack” aid that can help you find various long running operations such as fsync(). I used this late in the 3.36 cycle to fix a bunch of I/O happening on GNOME Shell’s compositor thread. Select the “Speedtrack” aid, and disable the “Callgraph” as that will clash with speedtrack currently. This also uses an LD_PRELOAD so you’ll have to spawn the application just like for memory tracking.
The aid will give you callgraphs of various things that happened in your main thread that you might want to avoid doing. Stuff like fsync(), read() and more. It also creates marks for the duration of these calls so you can track down how long they ran for.
You can also see how long some operations have taken. Here we see g_main_context_iteration() took 22 milliseconds. On a 60hz system, that can’t be good because we either missed a frame or took too long to do something to be able to submit our frame in time. You can select the time range by activating this row. In the future we want this to play better with callgraphs so you can see what was sampled during that timespan.
Anyway, I hope that gives you some insight into how to use things!
I spent some time this cycle porting GtkSourceView to GTK 4. It was a good opportunity to help me catch up on how GTK 4’s internals have changed into something modern. It gave me a chance to fix a few pot-holes along the way too.
One of the pot-holes was one I left in GtkTextView years ago. When I plumbed the pixelcache into GTK 3’s TextView I had only cached the primary text content. It seemed fine at the time because the gutters (used for line numbers) is just not that many pixels. So if we have to re-generate that every frame, so be it.
However, in a HiDPI world and 4k monitors on our laps things start to get… warm. So while changing the drawing model in GtkTextView we decided to make the GtkTextView gutters real widgets. Doing so means that GtkSourceGutterRenderer will be real GtkWidget‘s going forward and can do all sorts of neat stuff widgets can do.
But to address the speed of rendering we needed a better way to avoid walking the text btree linearly so many times while rendering the gutter. I’ve added a new class GtkSourceGutterLines to allow collecting information about the text buffer in one-pass. The renderers can then use that information when creating render nodes to avoid further tree scans.
I have some other plans for what I’d like to see before a 5.0 of GtkSourceView. I’ve already written a more memory-compact undo/redo engine for GTK’s GtkTextView, GtkEntry, GtkText, and friends which allowed me to delete that code from the GtkSourceView port. Better yet, you get undo/redo in all the places you would, well, expect it.
In particular I would like to see the async+GListModel based API for completion from Builder land upstream. Builder also has a robust snippet engine which could be reusable from GtkSourceView as that is a fairly useful thing across editors. Perhaps we could extract Builder’s indenter APIs and movements engine too. These are used by Builder’s Vim emulation quite heavily, for example.
If you like following development of stuff I’m doing you can always get that fix here on Twitter given my blogging infrequency.