The "priority" field of extmarks can be used to set priorities of
extmarks which dictates which highlight group a range will actually have
when there are multiple extmarks applied. However, when multiple
extmarks have the same priority, the only way to enforce an actual
priority is through the order in which the extmarks are set.
It is not always possible or desirable to set extmarks in a specific
order, however, so we add a new "subpriority" field that explicitly
enforces the ordering of extmarks that have the same priority.
For now this will be used only to enforce priority of treesitter
highlights. A single node in a treesitter tree may match multiple
captures, in which case that node will have multiple extmarks set. The
order in which captures are returned from the treesitter API is not
_necessarily_ in the same order they are defined in a query file, so we
use the new subpriority field to force that ordering.
For now subpriorites are not documented and are not meant to be used by
external code, and it only applies to ephemeral extmarks. We indicate
the "private" nature of subpriorities by prefixing the field name with
an "_".
Problem: Marks moved by undo may be lost to "b_signcols.count".
Solution: Count signs for each undo object separately instead of
once for the entire undo.
Problem: Some core syntax highlight groups are cleared with intention to
always be shown without additional highlighting. This doesn't always
work as intended, especially with fallback mechanism of @-groups.
Example: `Statement`/`Keyword` group shown in help code blocks
(`@markup.raw`) is shown as bold (from `Statement`) cyan (from
`@markup.raw`) instead of bold grey.
Solution: Explicitly use normal grey foreground in syntax groups where
it was previously implicitly assumed.
Extmarks can contain URLs which can then be drawn in any supporting UI.
In the TUI, for example, URLs are "drawn" by emitting the OSC 8 control
sequence to the TTY. On terminals which support the OSC 8 sequence this
will create clickable hyperlinks.
URLs are treated as inline highlights in the decoration subsystem, so
are included in the `DecorSignHighlight` structure. However, unlike
other inline highlights they use allocated memory which must be freed,
so they set the `ext` flag in `DecorInline` so that their lifetimes are
managed along with other allocated memory like virtual text.
The decoration subsystem then adds the URLs as a new highlight
attribute. The highlight subsystem maintains a set of unique URLs to
avoid duplicating allocations for the same string. To attach a URL to an
existing highlight attribute we call `hl_add_url` which finds the URL in
the set (allocating and adding it if it does not exist) and sets the
`url` highlight attribute to the index of the URL in the set (using an
index helps keep the size of the `HlAttrs` struct small).
This has the potential to lead to an increase in highlight attributes
if a URL is used over a range that contains many different highlight
attributes, because now each existing attribute must be combined with
the URL. In practice, however, URLs typically span a range containing a
single highlight (e.g. link text in Markdown), so this is likely just a
pathological edge case.
When a new highlight attribute is defined with a URL it is copied to all
attached UIs with the `hl_attr_define` UI event. The TUI manages its own
set of URLs (just like the highlight subsystem) to minimize allocations.
The TUI keeps track of which URL is "active" for the cell it is
printing. If no URL is active and a cell containing a URL is printed,
the opening OSC 8 sequence is emitted and that URL becomes the actively
tracked URL. If the cursor is moved while in the middle of a URL span,
we emit the terminating OSC sequence to prevent the hyperlink from
spanning multiple lines.
This does not support nested hyperlinks, but that is a rare (and,
frankly, bizarre) use case. If a valid use case for nested hyperlinks
ever presents itself we can address that issue then.
'foldtext' can be set to an empty string to disable and render the
line with:
- extmark highlight
- syntax highlighting
- search highlighting
- no line wrapping
- spelling
- conceal
- inline virtual text
- respects `fillchars:fold`
Currently normal virtual text is not displayed
Co-authored-by: zeertzjq <zeertzjq@outlook.com>
When an embedded Nvim instance changes its current directory a "chdir"
UI event is emitted. Attached UIs can use this information however they
wish. In the TUI it is used to synchronize the cwd of the TUI process
with the cwd of the embedded Nvim process.
Problem: Wrong "clear" argument passed to buf_signcols_count_range
when initializing "b_signcols.count" for the first time.
Solution: Pass kFalse so that the "nested" counter is not incorrectly
decremented.
Problem: The 'statuscolumn' is not redrawn on the wrapped part of a
line when moving the cursor with 'relativenumber' set.
Solution: Redraw the 'statuscolumn' for the entire line height in the
"col_rows" win_line() code path.
Problem: Some edge cases to the old (pre-#26406) and current "b_signcols"
structure result in an incorrectly sized "auto" 'signcolumn'.
Solution: * Implement a simpler 'signcolumn' validation strategy by immediately
counting the number of signs in a range upon sign insertion and
deletion. Decrease in performance here but there is a clear path
forward to decreasing this performance hit by moving signs to a
dedicated marktree, or by adding meta-data to the existing
marktree which may be queried more efficiently?
* Also replace "max_count" and keep track of the number of lines with
a certain number of signs. This makes it so that it is no longer
necessary to scan the entire buffer when the maximum number of signs
decreases. This likely makes the commit a net increase in performance.
* To ensure correctness we also have re-initialize the count for an
edited region that spans multiple lines. Such an edit may move the
signs within it. Thus we count and decrement before splicing the
marktree and count and increment after.
Problem: Current values of `StatusLine` and `StatusLineNC` are currently
designed to be visually distinctive while being not intrusive.
However, the compromise was more shifted towards "not intrusive".
After the feedback, statusline highlight groups should be designed to:
- Make current window clearly noticeable. Meaning `StatusLine` and
`StatusLineNC` should obviously differ.
- Make non-current windows clearly separable. Meaning `StatusLineNC`
and `Normal`/`NormalNC` should obviously differ.
Solution:
- Update `StatusLineNC` to have more visible background.
- Update `StatusLine` to be inverted variant of `StatusLineNC`.
- Update `WinBar` and `WinBarNC` to not link to `StatusLine` and
`StatusLineNC` because it makes two goals harder to achieve.
- Update `TabLine` to link to `StatusLineNC` instead of `StatusLine`
to not be very visually intrusive.
Problem:
Many decoration providers (treesitter injection highlighting, semantic
token highlighting, inlay hint) rely on the correctness of the `botline`
argument of `on_win` callback. However, `botline` can be smaller than
the actual line number of the last displayed line if some lines are
folded. In such cases, some decorations will be missing in the lines not
covered by `botline`.
Solution:
Validate `botline` when invoking `on_win`.
NOTE:
It seems that the old code was deliberately avoiding this presumably due
to performance reasons. However, I haven't experienced noticeable lag
after this change, and I believe the cost of botline computation would
be much smaller than the cost of decoration providers.
Problem: Default number column has incorrect width after 'statuscolumn'
is unset due to an error, but was also truncated.
Solution: Reverse 'statuscolumn' error and truncate return branches.
Problem: Unable to predict which byte-offset to place virtual text to
make it repeat visually in the wrapped part of a line.
Solution: Add a flag to nvim_buf_set_extmark() that causes virtual
text to repeat in wrapped lines.
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.
Problem: Bundled 'vim' color scheme is written in Vimscript which
implicitly assumes that the file is ported from Vim.
This is not the case, at it is currently the Neovim's way of providing
backward compatibility for color schemes.
Solution: Rewrite it in Lua to indicate that this runtime file comes
from Neovim.
Problem: Updating default color scheme produced some feedback.
Solution: Address the feedback.
Outline of the changes:
- Colors `Grey1` and `Grey2` are made a little bit more extreme (dark -
darker, light - lighter) to increase overall contrast.
- `gui` colors are treated as base with `cterm` colors falling back to
using 0-15 colors which come from terminal emulator.
- Update highlight group definition to not include attribute definition
if it is intended to staty uncolored.
- Tweak some specific highlight groups.
- Add a list of Neovim specific highlight groups which are now defined
differently in a breaking way.
- Minor tweaks in several other places related to default color scheme.
Problem: Unpaired marks are invalidated if its column is deleted,
which may just be a "placeholder" column, e.g. for signs.
Solution: Only remove unpaired marks if its entire row is deleted.
This is the command invoked repeatedly to make the changes:
:%s/^\(.*\)|\%(\*\(\d\+\)\)\?$\n\1|\%(\*\(\d\+\)\)\?$/\=submatch(1)..'|*'..(max([str2nr(submatch(2)),1])+max([str2nr(submatch(3)),1]))/g
Allow a "*count" suffix in a screen line to repeat the screen line for
"count" times.
The change is made to Screen:expect() and Screen:get_snapshot() instead
of Screen:render() so that screen expectations generated using code can
still work and test failures can still be readable.
A snapshot is now also printed on failure so that there is no need to
run the test again with Screen:snapshot_util().
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.
Currently, the value of $COLORTERM in :terminal in tests depends on
outer environment because of 'notermguicolors'.
If $COLORTERM is not set in :terminal, an inner Nvim instance will try
to detect 'termguicolors' support, which may interfere with tests.
So set 'termguicolors' in outer Nvim instance unless $COLORTERM needs to
be overridden, and unset it in inner Nvim instance when running TUI.
Set 'notermguicolors' in tests which spawn a child Nvim process to force
existing tests to use 16 colors. Also refactor the child process
invocation to make things a little bit less messy.
If the color scheme is changed in a startup script, nvim used to send
multiple default_colors_set events, one for the default color scheme
and one for the user's chosen color scheme. This would cause flicker in
some UI:s. Throttle this event until we actually start drawing on the
screen.
fixes#26372
Problem:
The test for 'nofsync' swapfile preservation on a deadly signal, does
not actually assert anything.
followup to 1fd29a2884
Solution:
Check that swapfile contents are present after getting SIGTERM.
TODO: this doesn't really verify that 'fsync' was called; it still
passes with this patch:
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 216e39f3e81c..7a635520401d 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -838,7 +838,7 @@ void preserve_exit(const char *errmsg)
if (errmsg != NULL) {
os_errmsg("Vim: preserving files...\r\n");
}
- ml_sync_all(false, false, true); // preserve all swap files
+ ml_sync_all(false, false, false); // preserve all swap files
break;
}
}
However it correctly fails with this patch, at least:
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 216e39f3e81c..f2306c310ddc 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -838,7 +838,6 @@ void preserve_exit(const char *errmsg)
if (errmsg != NULL) {
os_errmsg("Vim: preserving files...\r\n");
}
- ml_sync_all(false, false, true); // preserve all swap files
break;
}
}
Problem: 'breakindent' is not drawn after diff filler lines.
Solution: Correct check for whether 'breakindent' should be drawn.
closes: vim/vim#13624588f20dece
Cherry-pick Test_diff_with_syntax() change from patch 9.0.1257.
Problem: Default color scheme is suboptimal.
Solution: Start using new color scheme. Introduce new `vim` color scheme
for opt-in backward compatibility.
------
Main design ideas
- Be "Neovim branded".
- Be minimal for 256 colors with a bit more shades for true colors.
- Be accessible through high enough contrast ratios.
- Be suitable for dark and light backgrounds via exchange of dark and
light palettes.
------
Palettes
- Have dark and light variants. Implemented through exporeted
`NvimDark*` and `NvimLight*` hex colors.
- Palettes have 4 shades of grey for UI elements and 6 colors (red,
yellow, green, cyan, blue, magenta).
- Actual values are computed procedurally in Oklch color space based on
a handful of hyperparameters.
- Each color has a 256 colors variant with perceptually closest color.
------
Highlight groups
Use:
- Grey shades for general UI according to their design.
- Bold text for keywords (`Statement` highlight group). This is an
important choice to increase accessibility for people with color
deficiencies, as it doesn't rely on actual color.
- Green for strings, `DiffAdd` (as background), `DiagnosticOk`, and some
minor text UI elements.
- Cyan as main syntax color, i.e. for function usage (`Function`
highlight group), `DiffText`, `DiagnosticInfo`, and some minor text UI
elements.
- Red to generally mean high user attention, i.e. errors; in particular
for `ErrorMsg`, `DiffDelete`, `DiagnosticError`.
- Yellow very sparingly only with true colors to mean mild user
attention, i.e. warnings. That is, `DiagnosticWarn` and `WarningMsg`.
- Blue very sparingly only with true colors as `DiagnosticHint` and some
additional important syntax group (like `Identifier`).
- Magenta very carefully (if at all).
------
Notes
- To make tests work without relatively larege updates, each one is
prepended with an equivalent of the call `:colorscheme vim`.
Plus some tests which spawn new Neovim instances also now use 'vim'
color scheme.
In some cases tests are updated to fit new default color scheme.
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.
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.
Problem: buffer text with composing chars are converted from UTF-8
to an array of up to seven UTF-32 values and then converted back
to UTF-8 strings.
Solution: Convert buffer text directly to UTF-8 based schar_T values.
The limit of the text size is now in schar_T bytes, which is currently
31+1 but easily could be raised as it no longer multiplies the size
of the entire screen grid when not used, the full size is only required
for temporary scratch buffers.
Also does some general cleanup to win_line text handling, which was
unnecessarily complicated due to multibyte rendering being an "opt-in"
feature long ago. Nowadays, a char is just a char, regardless if it consists
of one ASCII byte or multiple bytes.
The 'termsync' option enables a mode (provided the underlying terminal
supports it) where all screen updates during a redraw cycle are buffered
and drawn together when the redraw is complete. This eliminates tearing
or flickering in cases where Nvim redraws slower than the terminal
redraws the screen.
refactor!: `vim.lsp.inlay_hint()` -> `vim.lsp.inlay_hint.enable()`
Problem:
The LSP specification allows inlay hints to include tooltips, clickable
label parts, and code actions; but Neovim provides no API to query for
these.
Solution:
Add minimal viable extension point from which plugins can query for
inlay hints in a range, in order to build functionality on top of.
Possible Next Steps
---
- Add `virt_text_idx` field to `vim.fn.getmousepos()` return value, for
usage in mappings of `<LeftMouse>`, `<C-LeftMouse>`, etc