We do not need an enum to keep track of what place in win_line() we
currently are at. We already have a variable which keeps track where
in the code we currently are (and thus what part of the line we are
currently rendering), it is called the _program counter_. When we need
non-linear or self-referential control-flow anyway for a laugh, we
have a mechanism for that, it is called _function calls_.
Do not "save" and "restore" the wlv->n_extra state every time the
columns are to be drawn. This sort of thing needs to go away. Instead of
setting the n_extra variables and then going to the outer while loop,
the text in the columns can be rendered by just simply putting the text
into the cells of the screen line, right away. Even in nvim this can be
tricky sometimes, luckily we can use function calls to abstract this
logic, which means that this handy data structure called the _call
stack_ is handling saving away state temporarily, and restoring it back
when we need it again.
Lastly, but not least, as we now have direct control how signs
are rendered, these can be stored as schar_T[2] and be directly
put on screen as such.
buf_signcols_validate_range() is only called in a buffer with signs, in
which the marktree is thus non-empty. Also remove a redundant comment,
condition and variable.
*** CID 470331: Error handling issues (CHECKED_RETURN)
/src/nvim/decoration.c: 824 in buf_signcols_validate_range()
818
819 // Allocate an array of integers holding the overlapping signs in the range.
820 assert(row2 >= row1);
821 int *overlap = xcalloc(sizeof(int), (size_t)(row2 + 1 - row1));
822
823 // First find the number of overlapping signs at "row1".
>>> CID 470331: Error handling issues (CHECKED_RETURN)
>>> Calling "marktree_itr_get_overlap" without checking return value (as is done elsewhere 4 out of 5 times).
824 marktree_itr_get_overlap(buf->b_marktree, currow, 0, itr);
825 while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
826 if (!mt_invalid(pair.start) && pair.start.flags & MT_FLAG_DECOR_SIGNTEXT) {
827 overlap[0]++;
828 }
829 }
Problem: The entire marktree needs to be traversed each time a sign is
removed from the sentinel line.
Solution: Remove sentinel line and instead keep track of the number of
lines that hold up the 'signcolumn' in "max_count". Adjust this
number for added/removed signs, and set it to 0 when the
maximum number of signs on a line changes. Only when
"max_count" is decremented to 0 due to sign removal do we need
to check the entire buffer.
Also replace "invalid_top" and "invalid_bot" with a map of
invalid ranges, further reducing the number of lines to be
checked.
Also improve tree traversal when counting the number of signs.
Instead of looping over the to be checked range and counting
the overlap for each row, keep track of the overlap in an
array and add this to the count.
Problem: Signcolumn width does not increase when ranged sign does not
start at sentinel line.
Solution: Handle paired range of added sign when checking signcols.
decor->text.str pointer must go. This removes it for conceal char,
in preparation for a larger PR which will also handle the sign case.
By actually allowing composing chars for a conceal chars, this
becomes a feature and not just a refactor, as a bonus.
Problem: Unable to identify legacy signs when fetching extmarks with
`nvim_buf_get_extmarks()`.
Solution: Add "sign_name" to the extmark detail array.
Add some misc. changes as follow-up to #25724
Remove the monolithic Decoration struct. Before this change, each extmark
could either represent just a hl_id + priority value as a inline
decoration, or it would take a pointer to this monolitic 112 byte struct
which has to be allocated.
This change separates the decorations into two pieces: DecorSignHighlight
for signs, highlights and simple set-flag decorations (like spell,
ui-watched), and DecorVirtText for virtual text and lines.
The main separation here is whether they are expected to allocate more
memory. Currently this is not really true as sign text has to be an
allocated string, but the plan is to get rid of this eventually (it can
just be an array of two schar_T:s). Further refactors are expected to
improve the representation of each decoration kind individually. The
goal of this particular PR is to get things started by cutting the
Gordian knot which was the monolithic struct Decoration.
Now, each extmark can either contain chained indicies/pointers to
these kinds of objects, or it can fit a subset of DecorSignHighlight
inline.
The point of this change is not only to make decorations smaller in
memory. In fact, the main motivation is to later allow them to grow
_larger_, but on a dynamic, on demand fashion. As a simple example, it
would be possible to augment highlights to take a list of multiple
`hl_group`:s, which then would trivially map to a chain of multiple
DecorSignHighlight entries.
One small feature improvement included with this refactor itself, is
that the restriction that extmarks cannot be removed inside a decoration
provider has been lifted. These are instead safely lifetime extended
on a "to free" list until the current iteration of screen drawing is done.
NB: flags is a mess. but DecorLevel is useless, this slightly less so
Problem: The legacy signlist data structures and associated functions are
redundant since the introduction of extmark signs.
Solution: Store signs defined through the legacy commands in a hashmap, placed
signs in the extmark tree. Replace signlist associated functions.
Usage of the legacy sign commands should yield no change in behavior with the
exception of:
- "orphaned signs" are now always removed when the line it is placed on is
deleted. This used to depend on the value of 'signcolumn'.
- It is no longer possible to place multiple signs with the same identifier
in a single group on multiple lines. This will now move the sign instead.
Moreover, both signs placed through the legacy sign commands and through
|nvim_buf_set_extmark()|:
- Will show up in both |sign-place| and |nvim_buf_get_extmarks()|.
- Are displayed by increasing sign identifier, left to right.
Extmark signs used to be ordered decreasingly as opposed to legacy signs.
We already have an extensive suite of static analysis tools we use,
which causes a fair bit of redundancy as we get duplicate warnings. PVS
is also prone to give false warnings which creates a lot of work to
identify and disable.
Problem: No way to have extmarks automatically removed when the range it
is attached to is deleted.
Solution: Add new 'invalidate' property that will hide a mark when the
entirety of its range is deleted. When "undo_restore" is set
to false, delete the mark from the buffer instead.
It is a design goal of extmarks that they allow precise tracking
of changes across undo/redo, including restore the exact positions
after a do/undo or undo/redo cycle. However this behavior is not useful
for all usecases. Many plugins won't keep marks around for long after
text changes, but uses them more like a cache until some external source
(like LSP semantic highlights) has fully updated to changed text and
then will explicitly readjust/replace extmarks as needed.
Add a "undo_restore" flag which is true by default (matches existing
behavior) but can be set to false to opt-out of this behavior.
Delete dead u_extmark_set() code.
The removes the previous restriction that nvim_buf_set_extmark()
could not be used to highlight arbitrary multi-line regions
The problem can be summarized as follows: let's assume an extmark with a
hl_group is placed covering the region (5,0) to (50,0) Now, consider
what happens if nvim needs to redraw a window covering the lines 20-30.
It needs to be able to ask the marktree what extmarks cover this region,
even if they don't begin or end here.
Therefore the marktree needs to be augmented with the information covers
a point, not just what marks begin or end there. To do this, we augment
each node with a field "intersect" which is a set the ids of the
marks which overlap this node, but only if it is not part of the set of
any parent. This ensures the number of nodes that need to be explicitly
marked grows only logarithmically with the total number of explicitly
nodes (and thus the number of of overlapping marks).
Thus we can quickly iterate all marks which overlaps any query position
by looking up what leaf node contains that position. Then we only need
to consider all "start" marks within that leaf node, and the "intersect"
set of that node and all its parents.
Now, and the major source of complexity is that the tree restructuring
operations (to ensure that each node has T-1 <= size <= 2*T-1) also need
to update these sets. If a full inner node is split in two, one of the
new parents might start to completely overlap some ranges and its ids
will need to be moved from its children's sets to its own set.
Similarly, if two undersized nodes gets joined into one, it might no
longer completely overlap some ranges, and now the children which do
needs to have the have the ids in its set instead. And then there are
the pivots! Yes the pivot operations when a child gets moved from one
parent to another.
fix(extmarks): disallow removing extmarks in on_lines callbacks
decor_redraw_start (which runs before decor_providers_invoke_lines) gets
references for the extmarks on a specific line. If these extmarks are
deleted in on_lines callbacks then this results in a heap-use-after-free
error.
Fixes#22801
Problem: Cursor displayed in wrong position after removing text prop. (Ben
Jackson)
Solution: Invalidate the cursor position. (closesvim/vim#10898)
326c5d36e7
Co-authored-by: Bram Moolenaar <Bram@vim.org>
extranges and a bunch of other improvements are coming for 0.10
This gets in some minor surrounding API changes to avoid rebase
conflicts until then.
- decorations will be able to be specific to windows
- adjust deletion API to fit with extranges
Problem: When a folded line has virtual lines attached, the following
problems occur:
- The virtual lines are drawn empty.
- The 'foldtext' line is drawn empty.
- The cursor is drawn incorrectly.
Solution: Check whether virtual lines belong to a folded line.
Fix#17027Fix#19557Fix#21837
Co-authored-by: zeertzjq <zeertzjq@outlook.com>