Smash Bros. example: https://www.youtube.com/watch?v=TqsXl8rj5q0 (watch at 0.25x speed to see how they animate it)
I see two places where we can give this responsibility:
- Rope
- Editor
The rope usually keeps track of the TextPos and byte pos of text modifications.
At the same time, sometimes we make modifications that are composed of calling multiple rope modification functions. Ideally, we’d like to merge these into a single edit.
For example, inserting a newline in between opening + closing delimiters causes the text to be placed at increased indentation, and the closing delimiter to be put on a new, dedented line.
Internally, we do this by inserting the text on increased indentation line, and then inserting closing delimiter on decreased indentation line. Two edits.
It makes more sense to combine them. If we implement undo, we don’t want the user to undo the closing delimiter insert, and then the text insert.
On the other hand, there are definitely situations where an edit is simply composed of multiple, non-overlapping edits. For example, search and replace all matches in a file.
TSEdit properties:
- start => the point where the text was added
- old_end => same as start
- new_end => length of insertion
TSEdit properties:
- start => the start of the range of text to be deleted
- old_end => the end of the range of text to be deleted
- new_end => 0
TSEdit properties:
- start => the start of the range of text to be deleted
- old_end => the end of the range of text to be deleted
- new_end => the new end
Command palette appears in a minibuffer, heavily inspired by Doom Emacs.
For now, just one single minibuffer.
The minibuffer contains:
- A text input
- List of results
The first consideration is how the input should behave. Ideally, this text input should enable Vim keybindings. That then brings to rise the question of how we make that happen.
Currently, all Vim keybindings / text editing operations are inside the Editor
struct/module. However, it is designed specifically for a regular buffer of text.
Some text editing operations simply don’t map onto this minibuffer text input.
For example, the `enter` key submits the input. There is only a single-line allowed.
One question is whether or not we actually want this behaviour. Would it be nice to allow multi-line input? The view of the input starts as single line, and as the user adds newlines, it grows?
There are a lot of questions that arise here. Note that the input is greatly simplified if we choose not to allow Vim keybindings.
The main problem of allowing Vim keybindings is that it requires slight alterations to how the Vim commands are handled.
Doom Emacs solves this by disallowing Vim in minibuffer inputs, same as VSCode.
In zsh with Vi editing they take an interesting approach. Typically in terminal emulators, the \
key is required to allow multiline input.
With zsh + Vi, you can press o
to create a new line, and each line will be interpreted as a separate command. On the UI, terminal emulators have their input at the bottom, so the text input grows upwards as newlines are added
- Move
-
Left, right, down, up, line start/end, wW/bB/eE, find, matching pair. All of these can stay the same.
When moving up and down, if going past text range, will go to option instead.
- delete/change/yank
- all of these can stay the same
- insert
-
has to look for newline and avoid it
actually no, in emacs you can copy/paste and it adds newlines
- insert newline
- no op on minibuffer input mode
- pressing enter
- if the minibuffer selection allows for any arbitrary input, submits otherwise enter does nothing until the user selects an option
- what we want
-
- current file path relative to project root
- current line / col
- vim mode
Resources:
- ghostty implementation
- freetype-gl implementation
- RectangleBinPack implementation
Couple things to note:
- Building atlas is expensive (binpacking and actually producing the texture)
- We should try to batch the work as much as possible
- e.g. in a frame we should rebuild the atlas once
- How can we do this?
-
Does rebuilding the atlas cause existing glyphs to have different texel coordinates?
I see two options:
- Do a pass over all of the visible text, collect new glyphs, rebuild the atlas.
- Rebuild the atlas as we build the text geometry
The first choice is optimal for scenarios where we encounter a lot of new glyphs, avoiding recalculating the atlas multiple times.
The first choice means we will either construct the CoreText data structures: CTLine, CTGlyphRun, etc. twice, or we cache them in the function. Caching them doesn’t sound like a bad option. We also call
.autorelease()
on these objects so memory wise it’s also fine to cache.Note: If it is inexpensive to only recalculate the atlas, that is, without creating the texture, then the choice probably doesn’t matter.
There are a couple places where there might be some discrepancies from converting integers <-> floats:
rewrite CGBitmapContextCreate to be nullable. Don’t use ?CGContextRef though because that doesn’t work
When we build text geometry, we iterate text line by line. On each line, we call self.font.lookup_glyph_rects()
which will load each glyph from the line, adding any newly encountered glyphs to the atlas, and possibly causing the atlas to resize.
If the atlas resizes, all the texcoords of vertices from previous lines will be invalid, since they will be normalized to the previous dimensions of the atlas.
there are probably lots of places where i am not freeing objc objects
- Determine where to render diagnostics
- Render diagnostics
Problem: need the baseline of each line. How to get this information efficiently?
Two things here:
- Need to compute it. Where/when?
- Simple: compute in
Instance.from_text_vertices()
-
It’s not really needed elsewhere, so why bother with any place else?
We don’t want to compute this for every char
- Simple: compute in
- Do we need to cache it? If so, where/when?
(refactor) coalesce the text geometry building code for build_text_geometry()
and build_line_number_geometry()
easiest to rebuild atlas from scratch again
later can do this off main thread so rendering isn’t interrupted
especially right now we rebuild all text when moving cursor as ez way to redraw cursor since it depends on text position
now we this charIdxToVertexIdx map we create in create_text_geometry, we can save this and use it to get their
vertices of a given char, so we can redraw the crusor without having to call create_text_geometry
again.
some ligatures like //
and ///
reuse the same glyph
but we are being lazy and not checking for this and adding redundant glyphs to the atlas
Good reosurce: https://phoboslab.org/log/2012/09/ejecta-2
look at these: https://www.shadertoy.com/view/lldGzr
https://drilian.com/2009/02/25/lightning-bolts/
https://www.shadertoy.com/view/3sXSD2
https://github.com/mattdesl/lwjgl-basics/wiki/LightningEffect
https://x.com/lightbulbfeed/status/1706441132992057604?s=20
lets you click on a piece of text, and a GUI pops up to edit the theme right there
will make a VIM user look insanely fast and coo l
for example pressing “d e”, you can prefix with some key and it will show you a preview of what will happen (like a GitHub inline diff, similar to what emacs does when you do search and replace)
would be cool to do this, for example swapping order of parameters
https://x.com/GrahamFleming_/status/1706356048821620907?s=20
https://x.com/xldenis/status/1706552511925002537?s=20
instead check if <= to current, if so just append otherwise create new
fn testFn(self: *Self) void {
switch (self) {
.Foo => {
},
}
}
start selection on comma
move to the .
on the line with .Foo
delete
it crashes
we use max_glyph_h
as the Y advance
but this is not correct
it needs to take into account glyphs that have their y origin lower
for example in the glyph ‘y’
i think this might be the ‘descent’ font metric