Identifying a low level editor bug by inspecting wrapped Rust objects
For quite some time we had an issue in the text editor related to the cursor getting stuck when it was at the end of a paragraph. It was annoying, but we did not know how to address it and it stayed open for half a year.
We knew that the issue was likely due to something in the lower levels, but we did not know what it could be. We did not even have a hypothesis.
Indeed, Glamorous Toolkit relies on Rust plugins. For example, the current implementation of the text editor is based on a wrapper over Rust objects. This is nice because it allows us to reuse external libraries. At the same time, to find issues visible in the high level editor, we need to understand how the low level objects work. To this end, we need at least the ability to inspect.
It was only until we extended the inspector to allow us to explore Rust objects that we could find the issue. The screenshot below shows the actual exploration that led to the bug resolution.
Inspecting the SkiaParagraph
instance shows an Info
view that is actually served by the wrapped Rust object. We can see how selecting the Line ranges
item, leads to an inspector over a Rust object.
So, what was the issue? Looking at the line ranges, we notice that index 6
is both the end of the first interval, and the beginning of the second interval. Once we saw this, it became obvious that 6 cannot belong in both. It turned out that the end of the interval was expected by the low level library to be exclusive, while our implementation assumed it is inclusive. Once we saw the problem, the fix was rather straightforward.
The differentiator between not knowing how to approach the problem at all and actually solving it was just a matter of seeing the problem in the first place. That is the power of Moldable Development.
Reproducing the exploration
Consider this editor:
stream := BlTextStream new. stream glamorousRegularFont fontSize: 40. stream next putAll: 'This is a piece of text in an editor. The editor is made out of '. stream next foreground: Color blue; bold; putAll: 'Pharo'. stream next putAll: ' paragraph objects. These paragraph objects have correspondent objects in the '. stream next foreground: Color red; bold; putAll: 'Rust'. stream next putAll: ' world which can be inspected seamlessly, too.'. editor := BrEditor new text: stream contents; aptitude: BrGlamorousRegularEditorAptitude new
The editor is made out of paragraph elements. In the current implenentation they are instances of BrTextEditorParagraphElement
. To draw themselves, these objects depend on SkiaParagraph
instances that are actually wrappers over Rust objects.
How does it work? Follow this:
First, inspect the editor above to make it render.
Then let's inspect one of the children and access the lower SkiaParagraph
instance:
editor children first buildParagraph layoutWithWidth: 400
The resulting inspector has an R
icon. If you press it, you will get to the Rust object.