Compare commits

...

6 Commits

Author SHA1 Message Date
luukvbaal
09785d68aa
Merge 10298f4533 into 02bc40c194 2024-12-19 12:24:44 +05:30
zeertzjq
02bc40c194
vim-patch:9.1.0945: ComplMatchIns highlight doesn't end after inserted text (#31628)
Problem:  ComplMatchIns highlight doesn't end after inserted text.
Solution: Handle ComplMatchIns highlight more like search highlight.
          Fix off-by-one error. Handle deleting text properly.
          (zeertzjq)

closes: vim/vim#16244

f25d8f9312
2024-12-18 23:59:03 +00:00
luukvbaal
160cbd0ef4
test(cursor_spec): global highlight definitions (#31613) 2024-12-18 19:06:16 +00:00
Gregory Anders
3db3947b0e
fix(terminal): restore cursor from 'guicursor' on TermLeave (#31620)
Fixes: https://github.com/neovim/neovim/issues/31612
2024-12-18 11:41:05 -06:00
Luuk van Baal
10298f4533 feat(treesitter): vertical conceal for conceal nodes
TSHighlighter now places marks for conceal_lines metadata. A new
internal decor provider callback _on_conceal_line was added that
instructs the highlighter to place conceal_lines marks whenever the
editor needs to know whether a line is concealed. The bundled markdown
queries use `conceal_lines` metadata to conceal code block fence lines.
2024-12-12 18:33:52 +01:00
Luuk van Baal
743ddaa764 feat(marks): add conceal_lines to nvim_buf_set_extmark()
Implement an extmark property that conceals lines vertically.
2024-12-12 18:32:39 +01:00
40 changed files with 938 additions and 231 deletions

View File

@ -60,7 +60,7 @@ hi('PmenuMatch', { link = 'Pmenu' })
hi('PmenuMatchSel', { link = 'PmenuSel' }) hi('PmenuMatchSel', { link = 'PmenuSel' })
hi('PmenuExtra', { link = 'Pmenu' }) hi('PmenuExtra', { link = 'Pmenu' })
hi('PmenuExtraSel', { link = 'PmenuSel' }) hi('PmenuExtraSel', { link = 'PmenuSel' })
hi('ComplMatchIns', { link = 'Normal' }) hi('ComplMatchIns', {})
hi('Substitute', { link = 'Search' }) hi('Substitute', { link = 'Search' })
hi('Whitespace', { link = 'NonText' }) hi('Whitespace', { link = 'NonText' })
hi('MsgSeparator', { link = 'StatusLine' }) hi('MsgSeparator', { link = 'StatusLine' })

View File

@ -2772,6 +2772,10 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts})
When a character is supplied it is used as |:syn-cchar|. When a character is supplied it is used as |:syn-cchar|.
"hl_group" is used as highlight for the cchar if provided, "hl_group" is used as highlight for the cchar if provided,
otherwise it defaults to |hl-Conceal|. otherwise it defaults to |hl-Conceal|.
• conceal_lines: string which should be either empty or a
single character. When provided, lines in the range are
not drawn at all; the next unconcealed line is drawn
instead.
• spell: boolean indicating that spell checking should be • spell: boolean indicating that spell checking should be
performed within this extmark performed within this extmark
• ui_watched: boolean that indicates the mark should be • ui_watched: boolean that indicates the mark should be

View File

@ -178,6 +178,7 @@ API
• |nvim__ns_set()| can set properties for a namespace • |nvim__ns_set()| can set properties for a namespace
• |vim.json.encode()| has an option to enable forward slash escaping • |vim.json.encode()| has an option to enable forward slash escaping
• New |nvim_buf_set_extmark()| flag `conceal_lines` to conceal an entire line.
DEFAULTS DEFAULTS
@ -303,6 +304,8 @@ TREESITTER
• |treesitter-directive-trim!| can trim all whitespace (not just empty lines) • |treesitter-directive-trim!| can trim all whitespace (not just empty lines)
from both sides of a node. from both sides of a node.
• |vim.treesitter.get_captures_at_pos()| now returns the `id` of each capture • |vim.treesitter.get_captures_at_pos()| now returns the `id` of each capture
• Bundled markdown highlight queries use `conceal_lines` metadata to conceal
code block fence lines vertically.
TUI TUI

View File

@ -493,10 +493,10 @@ capture marks comments as to be checked: >query
There is also `@nospell` which disables spellchecking regions with `@spell`. There is also `@nospell` which disables spellchecking regions with `@spell`.
*treesitter-highlight-conceal* *treesitter-highlight-conceal*
Treesitter highlighting supports |conceal| via the `conceal` metadata. By Treesitter highlighting supports |conceal| via the `conceal` and `conceal_lines`
convention, nodes to be concealed are captured as `@conceal`, but any capture metadata. By convention, nodes to be concealed are captured as `@conceal`, but
can be used. For example, the following query can be used to hide code block any capture can be used. For example, the following query can be used to hide
delimiters in Markdown: >query code block delimiters in Markdown: >query
(fenced_code_block_delimiter @conceal (#set! conceal "")) (fenced_code_block_delimiter @conceal (#set! conceal ""))
< <
@ -507,7 +507,13 @@ still highlighted the same as other operators: >query
"!=" @operator (#set! conceal "≠") "!=" @operator (#set! conceal "≠")
< <
Conceals specified in this way respect 'conceallevel'. To conceal an entire line (do not draw it at all), a query with `conceal_lines`
metadata can be used: >query
((comment) @comment @spell
(#set! conceal_lines ""))
<
Conceals specified in this way respect 'conceallevel' and 'concealcursor'.
*treesitter-highlight-priority* *treesitter-highlight-priority*
Treesitter uses |nvim_buf_set_extmark()| to set highlights with a default Treesitter uses |nvim_buf_set_extmark()| to set highlights with a default

View File

@ -686,6 +686,9 @@ function vim.api.nvim_buf_line_count(buffer) end
--- When a character is supplied it is used as `:syn-cchar`. --- When a character is supplied it is used as `:syn-cchar`.
--- "hl_group" is used as highlight for the cchar if provided, --- "hl_group" is used as highlight for the cchar if provided,
--- otherwise it defaults to `hl-Conceal`. --- otherwise it defaults to `hl-Conceal`.
--- - conceal_lines: string which should be either empty or a single
--- character. When provided, lines in the range are not drawn
--- at all; the next unconcealed line is drawn instead.
--- - spell: boolean indicating that spell checking should be --- - spell: boolean indicating that spell checking should be
--- performed within this extmark --- performed within this extmark
--- - ui_watched: boolean that indicates the mark should be drawn --- - ui_watched: boolean that indicates the mark should be drawn

View File

@ -234,6 +234,7 @@ error('Cannot require a meta file')
--- @field on_end? fun(_: "end", tick: integer) --- @field on_end? fun(_: "end", tick: integer)
--- @field _on_hl_def? fun(_: "hl_def") --- @field _on_hl_def? fun(_: "hl_def")
--- @field _on_spell_nav? fun(_: "spell_nav") --- @field _on_spell_nav? fun(_: "spell_nav")
--- @field _on_conceal_line? fun(_: "conceal_line")
--- @class vim.api.keyset.set_extmark --- @class vim.api.keyset.set_extmark
--- @field id? integer --- @field id? integer
@ -263,6 +264,7 @@ error('Cannot require a meta file')
--- @field line_hl_group? integer|string --- @field line_hl_group? integer|string
--- @field cursorline_hl_group? integer|string --- @field cursorline_hl_group? integer|string
--- @field conceal? string --- @field conceal? string
--- @field conceal_lines? string
--- @field spell? boolean --- @field spell? boolean
--- @field ui_watched? boolean --- @field ui_watched? boolean
--- @field undo_restore? boolean --- @field undo_restore? boolean

View File

@ -67,6 +67,8 @@ end
--- This state is kept during rendering across each line update. --- This state is kept during rendering across each line update.
---@field private _highlight_states vim.treesitter.highlighter.State[] ---@field private _highlight_states vim.treesitter.highlighter.State[]
---@field private _queries table<string,vim.treesitter.highlighter.Query> ---@field private _queries table<string,vim.treesitter.highlighter.Query>
---@field _conceal_line boolean?
---@field _conceal_checked table<integer, boolean>
---@field tree vim.treesitter.LanguageTree ---@field tree vim.treesitter.LanguageTree
---@field private redraw_count integer ---@field private redraw_count integer
local TSHighlighter = { local TSHighlighter = {
@ -114,7 +116,7 @@ function TSHighlighter.new(tree, opts)
self.bufnr = source self.bufnr = source
self.redraw_count = 0 self.redraw_count = 0
self._highlight_states = {} self._conceal_checked = {}
self._queries = {} self._queries = {}
-- Queries for a specific language can be overridden by a custom -- Queries for a specific language can be overridden by a custom
@ -122,6 +124,7 @@ function TSHighlighter.new(tree, opts)
if opts.queries then if opts.queries then
for lang, query_string in pairs(opts.queries) do for lang, query_string in pairs(opts.queries) do
self._queries[lang] = TSHighlighterQuery.new(lang, query_string) self._queries[lang] = TSHighlighterQuery.new(lang, query_string)
self._conceal_line = self._conceal_line or self._queries[lang]:query().conceal_line
end end
end end
@ -140,7 +143,7 @@ function TSHighlighter.new(tree, opts)
-- immediately afterwards will not error. -- immediately afterwards will not error.
if vim.g.syntax_on ~= 1 then if vim.g.syntax_on ~= 1 then
vim.cmd.runtime({ 'syntax/synload.vim', bang = true }) vim.cmd.runtime({ 'syntax/synload.vim', bang = true })
vim.api.nvim_create_augroup('syntaxset', { clear = false }) api.nvim_create_augroup('syntaxset', { clear = false })
end end
vim._with({ buf = self.bufnr }, function() vim._with({ buf = self.bufnr }, function()
@ -148,6 +151,7 @@ function TSHighlighter.new(tree, opts)
end) end)
self.tree:parse() self.tree:parse()
self:set_decoration_provider()
return self return self
end end
@ -160,6 +164,7 @@ function TSHighlighter:destroy()
if api.nvim_buf_is_loaded(self.bufnr) then if api.nvim_buf_is_loaded(self.bufnr) then
vim.bo[self.bufnr].spelloptions = self.orig_spelloptions vim.bo[self.bufnr].spelloptions = self.orig_spelloptions
vim.b[self.bufnr].ts_highlight = nil vim.b[self.bufnr].ts_highlight = nil
api.nvim_buf_clear_namespace(self.bufnr, ns, 0, -1)
if vim.g.syntax_on == 1 then if vim.g.syntax_on == 1 then
api.nvim_exec_autocmds('FileType', { group = 'syntaxset', buffer = self.bufnr }) api.nvim_exec_autocmds('FileType', { group = 'syntaxset', buffer = self.bufnr })
end end
@ -185,10 +190,14 @@ function TSHighlighter:prepare_highlight_states(srow, erow)
return return
end end
local highlighter_query = self:get_query(tree:lang()) local hl_query = self:get_query(tree:lang())
if hl_query:query().conceal_line and not self._conceal_line then
self._conceal_line = true
self:set_decoration_provider()
end
-- Some injected languages may not have highlight queries. -- Some injected languages may not have highlight queries.
if not highlighter_query:query() then if not hl_query:query() then
return return
end end
@ -198,7 +207,7 @@ function TSHighlighter:prepare_highlight_states(srow, erow)
tstree = tstree, tstree = tstree,
next_row = 0, next_row = 0,
iter = nil, iter = nil,
highlighter_query = highlighter_query, highlighter_query = hl_query,
}) })
end) end)
end end
@ -220,7 +229,10 @@ end
---@param changes Range6[] ---@param changes Range6[]
function TSHighlighter:on_changedtree(changes) function TSHighlighter:on_changedtree(changes)
for _, ch in ipairs(changes) do for _, ch in ipairs(changes) do
api.nvim__redraw({ buf = self.bufnr, range = { ch[1], ch[4] + 1 }, flush = false }) api.nvim__redraw({ buf = self.bufnr, range = { ch[1], ch[4] }, flush = false })
for i = ch[1], self._conceal_line and ch[4] or 0 do
self._conceal_checked[i] = false
end
end end
end end
@ -279,8 +291,10 @@ end
---@param self vim.treesitter.highlighter ---@param self vim.treesitter.highlighter
---@param buf integer ---@param buf integer
---@param line integer ---@param line integer
---@param is_spell_nav boolean ---@param on_spell boolean
local function on_line_impl(self, buf, line, is_spell_nav) ---@param on_conceal boolean
local function on_line_impl(self, buf, line, on_spell, on_conceal)
self._conceal_checked[line] = true
self:for_each_highlight_state(function(state) self:for_each_highlight_state(function(state)
local root_node = state.tstree:root() local root_node = state.tstree:root()
local root_start_row, _, root_end_row, _ = root_node:range() local root_start_row, _, root_end_row, _ = root_node:range()
@ -326,7 +340,7 @@ local function on_line_impl(self, buf, line, is_spell_nav)
local url = get_url(match, buf, capture, metadata) local url = get_url(match, buf, capture, metadata)
if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then if hl and end_row >= line and not on_conceal and (not on_spell or spell ~= nil) then
api.nvim_buf_set_extmark(buf, ns, start_row, start_col, { api.nvim_buf_set_extmark(buf, ns, start_row, start_col, {
end_line = end_row, end_line = end_row,
end_col = end_col, end_col = end_col,
@ -338,6 +352,16 @@ local function on_line_impl(self, buf, line, is_spell_nav)
url = url, url = url,
}) })
end end
if
(metadata.conceal_lines or metadata[capture] and metadata[capture].conceal_lines)
and #api.nvim_buf_get_extmarks(buf, ns, { start_row, 0 }, { start_row, 0 }, {}) == 0
then
api.nvim_buf_set_extmark(buf, ns, start_row, 0, {
end_line = end_row,
conceal_lines = '',
})
end
end end
if start_row > line then if start_row > line then
@ -357,7 +381,7 @@ function TSHighlighter._on_line(_, _win, buf, line, _)
return return
end end
on_line_impl(self, buf, line, false) on_line_impl(self, buf, line, false, false)
end end
---@private ---@private
@ -376,17 +400,45 @@ function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _)
self:prepare_highlight_states(srow, erow) self:prepare_highlight_states(srow, erow)
for row = srow, erow do for row = srow, erow do
on_line_impl(self, buf, row, true) on_line_impl(self, buf, row, true, false)
end end
self._highlight_states = highlight_states self._highlight_states = highlight_states
end end
---@private ---@private
---@param _win integer ---@param buf integer
---@param row integer
function TSHighlighter._on_conceal_line(_, _, buf, row)
local self = TSHighlighter.active[buf]
if not self or self._conceal_checked[row] then
return
end
-- Do not affect potentially populated highlight state.
local highlight_states = self._highlight_states
self:prepare_highlight_states(row, row)
on_line_impl(self, buf, row, false, true)
self._highlight_states = highlight_states
end
---@private
--- Clear conceal_lines marks whenever we redraw for a buffer change. Marks are
--- added back as either the _conceal_line or on_win callback comes across them.
function TSHighlighter._on_buf(_, buf)
local self = TSHighlighter.active[buf]
if not self then
return
end
api.nvim_buf_clear_namespace(buf, ns, 0, -1)
self._conceal_checked = {}
end
---@private
---@param buf integer ---@param buf integer
---@param topline integer ---@param topline integer
---@param botline integer ---@param botline integer
function TSHighlighter._on_win(_, _win, buf, topline, botline) function TSHighlighter._on_win(_, _, buf, topline, botline)
local self = TSHighlighter.active[buf] local self = TSHighlighter.active[buf]
if not self then if not self then
return false return false
@ -397,10 +449,15 @@ function TSHighlighter._on_win(_, _win, buf, topline, botline)
return true return true
end end
api.nvim_set_decoration_provider(ns, { ---@private
on_win = TSHighlighter._on_win, function TSHighlighter:set_decoration_provider()
on_line = TSHighlighter._on_line, api.nvim_set_decoration_provider(ns, {
_on_spell_nav = TSHighlighter._on_spell_nav, on_win = TSHighlighter._on_win,
}) on_line = TSHighlighter._on_line,
_on_spell_nav = TSHighlighter._on_spell_nav,
_on_conceal_line = self._conceal_line and TSHighlighter._on_conceal_line or nil,
on_buf = self._conceal_line and TSHighlighter._on_buf or nil,
})
end
return TSHighlighter return TSHighlighter

View File

@ -11,6 +11,7 @@ local M = {}
---@field lang string name of the language for this parser ---@field lang string name of the language for this parser
---@field captures string[] list of (unique) capture names defined in query ---@field captures string[] list of (unique) capture names defined in query
---@field info vim.treesitter.QueryInfo contains information used in the query (e.g. captures, predicates, directives) ---@field info vim.treesitter.QueryInfo contains information used in the query (e.g. captures, predicates, directives)
---@field conceal_line boolean whether this query sets conceal_lines metadata.
---@field query TSQuery userdata query object ---@field query TSQuery userdata query object
local Query = {} local Query = {}
Query.__index = Query Query.__index = Query
@ -30,6 +31,17 @@ function Query.new(lang, ts_query)
patterns = query_info.patterns, patterns = query_info.patterns,
} }
self.captures = self.info.captures self.captures = self.info.captures
-- Instruct highlighter to place marks when query contains conceal_lines metadata.
for _, preds in pairs(self.info.patterns) do
for _, pred in ipairs(preds) do
if vim.deep_equal(pred, { 'set!', 'conceal_lines', '' }) then
self.conceal_line = true
break
end
end
end
return self return self
end end

View File

@ -49,12 +49,12 @@
(fenced_code_block (fenced_code_block
(fenced_code_block_delimiter) @markup.raw.block (fenced_code_block_delimiter) @markup.raw.block
(#set! conceal "")) (#set! conceal_lines ""))
(fenced_code_block (fenced_code_block
(info_string (info_string
(language) @label (language) @label
(#set! conceal ""))) (#set! conceal_lines "")))
(link_destination) @markup.link.url (link_destination) @markup.link.url

View File

@ -475,6 +475,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// When a character is supplied it is used as |:syn-cchar|. /// When a character is supplied it is used as |:syn-cchar|.
/// "hl_group" is used as highlight for the cchar if provided, /// "hl_group" is used as highlight for the cchar if provided,
/// otherwise it defaults to |hl-Conceal|. /// otherwise it defaults to |hl-Conceal|.
/// - conceal_lines: string which should be either empty or a single
/// character. When provided, lines in the range are not drawn
/// at all; the next unconcealed line is drawn instead.
/// - spell: boolean indicating that spell checking should be /// - spell: boolean indicating that spell checking should be
/// performed within this extmark /// performed within this extmark
/// - ui_watched: boolean that indicates the mark should be drawn /// - ui_watched: boolean that indicates the mark should be drawn
@ -575,6 +578,11 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
} }
} }
if (HAS_KEY(opts, set_extmark, conceal_lines)) {
hl.flags |= kSHConcealLines;
has_hl = true;
}
if (HAS_KEY(opts, set_extmark, virt_text)) { if (HAS_KEY(opts, set_extmark, virt_text)) {
virt_text.data.virt_text = parse_virt_text(opts->virt_text, err, &virt_text.width); virt_text.data.virt_text = parse_virt_text(opts->virt_text, err, &virt_text.width);
if (ERROR_SET(err)) { if (ERROR_SET(err)) {
@ -793,6 +801,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
} }
} }
if (hl.flags & kSHConcealLines) {
decor_flags |= MT_FLAG_DECOR_CONCEAL_LINES;
}
DecorInline decor = DECOR_INLINE_INIT; DecorInline decor = DECOR_INLINE_INIT;
if (decor_alloc || decor_indexed != DECOR_ID_INVALID || url != NULL if (decor_alloc || decor_indexed != DECOR_ID_INVALID || url != NULL
|| schar_high(hl.conceal_char)) { || schar_high(hl.conceal_char)) {
@ -1052,6 +1064,7 @@ void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) *
{ "on_end", &opts->on_end, &p->redraw_end }, { "on_end", &opts->on_end, &p->redraw_end },
{ "_on_hl_def", &opts->_on_hl_def, &p->hl_def }, { "_on_hl_def", &opts->_on_hl_def, &p->hl_def },
{ "_on_spell_nav", &opts->_on_spell_nav, &p->spell_nav }, { "_on_spell_nav", &opts->_on_spell_nav, &p->spell_nav },
{ "_on_conceal_line", &opts->_on_conceal_line, &p->conceal_line },
{ NULL, NULL, NULL }, { NULL, NULL, NULL },
}; };

View File

@ -20,6 +20,7 @@ typedef struct {
LuaRefOf(("end" _, Integer tick)) on_end; LuaRefOf(("end" _, Integer tick)) on_end;
LuaRefOf(("hl_def" _)) _on_hl_def; LuaRefOf(("hl_def" _)) _on_hl_def;
LuaRefOf(("spell_nav" _)) _on_spell_nav; LuaRefOf(("spell_nav" _)) _on_spell_nav;
LuaRefOf(("conceal_line" _)) _on_conceal_line;
} Dict(set_decoration_provider); } Dict(set_decoration_provider);
typedef struct { typedef struct {
@ -51,6 +52,7 @@ typedef struct {
HLGroupID line_hl_group; HLGroupID line_hl_group;
HLGroupID cursorline_hl_group; HLGroupID cursorline_hl_group;
String conceal; String conceal;
String conceal_lines;
Boolean spell; Boolean spell;
Boolean ui_watched; Boolean ui_watched;
Boolean undo_restore; Boolean undo_restore;

View File

@ -811,6 +811,7 @@ typedef struct {
uint16_t wl_size; // height in screen lines uint16_t wl_size; // height in screen lines
char wl_valid; // true values are valid for text in buffer char wl_valid; // true values are valid for text in buffer
char wl_folded; // true when this is a range of folded lines char wl_folded; // true when this is a range of folded lines
linenr_T wl_foldend; // last buffer line number for folded line
linenr_T wl_lastlnum; // last buffer line number for logical line linenr_T wl_lastlnum; // last buffer line number for logical line
} wline_T; } wline_T;

View File

@ -206,9 +206,10 @@ static void changed_lines_invalidate_win(win_T *wp, linenr_T lnum, colnr_T col,
} else if (xtra != 0) { } else if (xtra != 0) {
// line below change // line below change
wp->w_lines[i].wl_lnum += xtra; wp->w_lines[i].wl_lnum += xtra;
wp->w_lines[i].wl_foldend += xtra;
wp->w_lines[i].wl_lastlnum += xtra; wp->w_lines[i].wl_lastlnum += xtra;
} }
} else if (wp->w_lines[i].wl_lastlnum >= lnum) { } else if (wp->w_lines[i].wl_foldend >= lnum) {
// change somewhere inside this range of folded lines, // change somewhere inside this range of folded lines,
// may need to be redrawn // may need to be redrawn
wp->w_lines[i].wl_valid = false; wp->w_lines[i].wl_valid = false;

View File

@ -12,6 +12,7 @@
#include "nvim/buffer_defs.h" #include "nvim/buffer_defs.h"
#include "nvim/change.h" #include "nvim/change.h"
#include "nvim/decoration.h" #include "nvim/decoration.h"
#include "nvim/decoration_provider.h"
#include "nvim/drawscreen.h" #include "nvim/drawscreen.h"
#include "nvim/extmark.h" #include "nvim/extmark.h"
#include "nvim/fold.h" #include "nvim/fold.h"
@ -125,6 +126,13 @@ void decor_redraw_sh(buf_T *buf, int row1, int row2, DecorSignHighlight sh)
redraw_buf_range_later(buf, row1 + 1, row2 + 1); redraw_buf_range_later(buf, row1 + 1, row2 + 1);
} }
} }
if (sh.flags & kSHConcealLines) {
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_buffer == buf) {
changed_window_setting(wp);
}
}
}
if (sh.flags & kSHUIWatched) { if (sh.flags & kSHUIWatched) {
redraw_buf_line_later(buf, row1 + 1, false); redraw_buf_line_later(buf, row1 + 1, false);
} }
@ -833,8 +841,56 @@ int sign_item_cmp(const void *p1, const void *p2)
return 0; return 0;
} }
static const uint32_t sign_filter[4] = {[kMTMetaSignText] = kMTFilterSelect, static const uint32_t conceal_filter[kMTMetaCount] = {[kMTMetaConcealLines] = kMTFilterSelect };
[kMTMetaSignHL] = kMTFilterSelect };
bool decor_conceal_line(win_T *wp, int row, bool check_cursor)
{
if (wp->w_p_cole < 2
|| (!check_cursor && ((row + 1 == wp->w_cursor.lnum) && !conceal_cursor_line(wp)))) {
return false;
}
size_t keys = wp->w_buffer->b_marktree->n_keys;
if (!buf_meta_total(wp->w_buffer, kMTMetaConcealLines)) {
decor_providers_invoke_conceal_line(wp, row);
return wp->w_buffer->b_marktree->n_keys > keys;
}
MTPair pair;
MarkTreeIter itr[1];
marktree_itr_get_overlap(wp->w_buffer->b_marktree, row, 0, itr);
while (marktree_itr_step_overlap(wp->w_buffer->b_marktree, itr, &pair)) {
if (mt_conceal_lines(pair.start) && ns_in_win(pair.start.ns, wp)) {
return true;
}
}
marktree_itr_step_out_filter(wp->w_buffer->b_marktree, itr, conceal_filter);
while (itr->x) {
MTKey mark = marktree_itr_current(itr);
if (mark.pos.row > row) {
break;
}
if (mt_conceal_lines(mark) && ns_in_win(pair.start.ns, wp)) {
return true;
}
marktree_itr_next_filter(wp->w_buffer->b_marktree, itr, row + 1, 0, conceal_filter);
}
decor_providers_invoke_conceal_line(wp, row);
return wp->w_buffer->b_marktree->n_keys > keys;
}
bool win_lines_concealed(win_T *wp)
{
return hasAnyFolding(wp)
|| (wp->w_p_cole >= 2
&& (conceal_provider || buf_meta_total(wp->w_buffer, kMTMetaConcealLines)));
}
static const uint32_t sign_filter[kMTMetaCount] = {[kMTMetaSignText] = kMTFilterSelect,
[kMTMetaSignHL] = kMTFilterSelect };
/// Return the sign attributes on the currently refreshed row. /// Return the sign attributes on the currently refreshed row.
/// ///
@ -856,7 +912,7 @@ void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[],
// TODO(bfredl): integrate with main decor loop. // TODO(bfredl): integrate with main decor loop.
marktree_itr_get_overlap(buf->b_marktree, row, 0, itr); marktree_itr_get_overlap(buf->b_marktree, row, 0, itr);
while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
if (!mt_invalid(pair.start) && mt_decor_sign(pair.start)) { if (!mt_invalid(pair.start) && mt_decor_sign(pair.start) && ns_in_win(pair.start.ns, wp)) {
DecorSignHighlight *sh = decor_find_sign(mt_decor(pair.start)); DecorSignHighlight *sh = decor_find_sign(mt_decor(pair.start));
num_text += (sh->text[0] != NUL); num_text += (sh->text[0] != NUL);
kv_push(signs, ((SignItem){ sh, pair.start.id })); kv_push(signs, ((SignItem){ sh, pair.start.id }));
@ -923,7 +979,7 @@ DecorSignHighlight *decor_find_sign(DecorInline decor)
} }
} }
static const uint32_t signtext_filter[4] = {[kMTMetaSignText] = kMTFilterSelect }; static const uint32_t signtext_filter[kMTMetaCount] = {[kMTMetaSignText] = kMTFilterSelect };
/// Count the number of signs in a range after adding/removing a sign, or to /// Count the number of signs in a range after adding/removing a sign, or to
/// (re-)initialize a range in "b_signcols.count". /// (re-)initialize a range in "b_signcols.count".
@ -1018,7 +1074,7 @@ bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col)
return has_virt_pos; return has_virt_pos;
} }
static const uint32_t lines_filter[4] = {[kMTMetaLines] = kMTFilterSelect }; static const uint32_t lines_filter[kMTMetaCount] = {[kMTMetaLines] = kMTFilterSelect };
/// @param apply_folds Only count virtual lines that are not in folds. /// @param apply_folds Only count virtual lines that are not in folds.
int decor_virt_lines(win_T *wp, int start_row, int end_row, VirtLines *lines, bool apply_folds) int decor_virt_lines(win_T *wp, int start_row, int end_row, VirtLines *lines, bool apply_folds)
@ -1049,7 +1105,8 @@ int decor_virt_lines(win_T *wp, int start_row, int end_row, VirtLines *lines, bo
int mrow = mark.pos.row; int mrow = mark.pos.row;
int draw_row = mrow + (above ? 0 : 1); int draw_row = mrow + (above ? 0 : 1);
if (draw_row >= start_row && draw_row < end_row if (draw_row >= start_row && draw_row < end_row
&& (!apply_folds || !hasFolding(wp, mrow + 1, NULL, NULL))) { && (!apply_folds || !(hasFolding(wp, mrow + 1, NULL, NULL)
|| decor_conceal_line(wp, mrow, false)))) {
virt_lines += (int)kv_size(vt->data.virt_lines); virt_lines += (int)kv_size(vt->data.virt_lines);
if (lines) { if (lines) {
kv_splice(*lines, vt->data.virt_lines); kv_splice(*lines, vt->data.virt_lines);
@ -1116,6 +1173,10 @@ void decor_to_dict_legacy(Dict *dict, DecorInline decor, bool hl_name, Arena *ar
PUT_C(*dict, "conceal", CSTR_TO_ARENA_OBJ(arena, buf)); PUT_C(*dict, "conceal", CSTR_TO_ARENA_OBJ(arena, buf));
} }
if (sh_hl.flags & kSHConcealLines) {
PUT_C(*dict, "conceal_lines", STRING_OBJ(cstr_as_string("")));
}
if (sh_hl.flags & kSHSpellOn) { if (sh_hl.flags & kSHSpellOn) {
PUT_C(*dict, "spell", BOOLEAN_OBJ(true)); PUT_C(*dict, "spell", BOOLEAN_OBJ(true));
} else if (sh_hl.flags & kSHSpellOff) { } else if (sh_hl.flags & kSHSpellOff) {

View File

@ -46,6 +46,7 @@ enum {
kSHSpellOn = 16, kSHSpellOn = 16,
kSHSpellOff = 32, kSHSpellOff = 32,
kSHConceal = 64, kSHConceal = 64,
kSHConcealLines = 128,
}; };
typedef struct { typedef struct {
@ -143,6 +144,7 @@ typedef struct {
LuaRef redraw_end; LuaRef redraw_end;
LuaRef hl_def; LuaRef hl_def;
LuaRef spell_nav; LuaRef spell_nav;
LuaRef conceal_line;
int hl_valid; int hl_valid;
bool hl_cached; bool hl_cached;

View File

@ -30,7 +30,7 @@ static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE;
#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \ #define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \
{ ns_id, kDecorProviderDisabled, LUA_NOREF, LUA_NOREF, \ { ns_id, kDecorProviderDisabled, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, LUA_NOREF, LUA_NOREF, \ LUA_NOREF, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, -1, false, false, 0 } LUA_NOREF, LUA_NOREF, -1, false, false, 0 }
static void decor_provider_error(DecorProvider *provider, const char *name, const char *msg) static void decor_provider_error(DecorProvider *provider, const char *name, const char *msg)
{ {
@ -92,6 +92,21 @@ void decor_providers_invoke_spell(win_T *wp, int start_row, int start_col, int e
} }
} }
void decor_providers_invoke_conceal_line(win_T *wp, int row)
{
for (size_t i = 0; i < kv_size(decor_providers); i++) {
DecorProvider *p = &kv_A(decor_providers, i);
if (p->state != kDecorProviderDisabled && p->conceal_line != LUA_NOREF) {
conceal_provider = true;
MAXSIZE_TEMP_ARRAY(args, 4);
ADD_C(args, INTEGER_OBJ(wp->handle));
ADD_C(args, INTEGER_OBJ(wp->w_buffer->handle));
ADD_C(args, INTEGER_OBJ(row));
decor_provider_invoke((int)i, "conceal_line", p->conceal_line, args, true);
}
}
}
/// For each provider invoke the 'start' callback /// For each provider invoke the 'start' callback
/// ///
/// @param[out] providers Decoration providers /// @param[out] providers Decoration providers
@ -264,6 +279,7 @@ void decor_provider_clear(DecorProvider *p)
NLUA_CLEAR_REF(p->redraw_line); NLUA_CLEAR_REF(p->redraw_line);
NLUA_CLEAR_REF(p->redraw_end); NLUA_CLEAR_REF(p->redraw_end);
NLUA_CLEAR_REF(p->spell_nav); NLUA_CLEAR_REF(p->spell_nav);
NLUA_CLEAR_REF(p->conceal_line);
p->state = kDecorProviderDisabled; p->state = kDecorProviderDisabled;
} }

View File

@ -7,6 +7,7 @@
#include "nvim/types_defs.h" // IWYU pragma: keep #include "nvim/types_defs.h" // IWYU pragma: keep
EXTERN bool provider_active INIT( = false); EXTERN bool provider_active INIT( = false);
EXTERN bool conceal_provider INIT( = false);
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "decoration_provider.h.generated.h" # include "decoration_provider.h.generated.h"

View File

@ -24,6 +24,7 @@
#include "nvim/change.h" #include "nvim/change.h"
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/cursor.h" #include "nvim/cursor.h"
#include "nvim/decoration.h"
#include "nvim/diff.h" #include "nvim/diff.h"
#include "nvim/drawscreen.h" #include "nvim/drawscreen.h"
#include "nvim/errors.h" #include "nvim/errors.h"
@ -2082,7 +2083,7 @@ int diff_check_with_linestatus(win_T *wp, linenr_T lnum, int *linestatus)
} }
// A closed fold never has filler lines. // A closed fold never has filler lines.
if (hasFolding(wp, lnum, NULL, NULL)) { if (hasFolding(wp, lnum, NULL, NULL) || decor_conceal_line(wp, lnum - 1, false)) {
return 0; return 0;
} }

View File

@ -955,7 +955,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
bool area_highlighting = false; // Visual or incsearch highlighting in this line bool area_highlighting = false; // Visual or incsearch highlighting in this line
int vi_attr = 0; // attributes for Visual and incsearch highlighting int vi_attr = 0; // attributes for Visual and incsearch highlighting
int area_attr = 0; // attributes desired by highlighting int area_attr = 0; // attributes desired by highlighting
int search_attr = 0; // attributes desired by 'hlsearch' int search_attr = 0; // attributes desired by 'hlsearch' or ComplMatchIns
int vcol_save_attr = 0; // saved attr for 'cursorcolumn' int vcol_save_attr = 0; // saved attr for 'cursorcolumn'
int decor_attr = 0; // attributes desired by syntax and extmarks int decor_attr = 0; // attributes desired by syntax and extmarks
bool has_syntax = false; // this buffer has syntax highl. bool has_syntax = false; // this buffer has syntax highl.
@ -969,7 +969,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
int spell_attr = 0; // attributes desired by spelling int spell_attr = 0; // attributes desired by spelling
int word_end = 0; // last byte with same spell_attr int word_end = 0; // last byte with same spell_attr
int cur_checked_col = 0; // checked column for current line int cur_checked_col = 0; // checked column for current line
bool extra_check = 0; // has syntax or linebreak bool extra_check = false; // has extra highlighting
int multi_attr = 0; // attributes desired by multibyte int multi_attr = 0; // attributes desired by multibyte
int mb_l = 1; // multi-byte byte length int mb_l = 1; // multi-byte byte length
int mb_c = 0; // decoded multi-byte character int mb_c = 0; // decoded multi-byte character
@ -1495,6 +1495,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
ptr = line + v; // "line" may have been updated ptr = line + v; // "line" may have been updated
} }
if ((State & MODE_INSERT) && in_curline && ins_compl_active()) {
area_highlighting = true;
}
win_line_start(wp, &wlv); win_line_start(wp, &wlv);
bool draw_cols = true; bool draw_cols = true;
int leftcols_width = 0; int leftcols_width = 0;
@ -1740,6 +1744,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
if (*ptr == NUL) { if (*ptr == NUL) {
has_match_conc = 0; has_match_conc = 0;
} }
// Check if ComplMatchIns highlight is needed.
if ((State & MODE_INSERT) && in_curline && ins_compl_active()) {
int ins_match_attr = ins_compl_col_range_attr((int)(ptr - line));
if (ins_match_attr > 0) {
search_attr = hl_combine_attr(search_attr, ins_match_attr);
}
}
} }
if (wlv.diff_hlf != (hlf_T)0) { if (wlv.diff_hlf != (hlf_T)0) {
@ -1787,16 +1799,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri); wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri);
} }
// Apply ComplMatchIns highlight if needed.
if (wlv.filler_todo <= 0
&& (State & MODE_INSERT) && in_curline && ins_compl_active()) {
int ins_match_attr = ins_compl_col_range_attr((int)(ptr - line));
if (ins_match_attr > 0) {
char_attr_pri = hl_combine_attr(char_attr_pri, ins_match_attr);
wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri);
}
}
if (draw_folded && has_foldtext && wlv.n_extra == 0 && wlv.col == win_col_offset) { if (draw_folded && has_foldtext && wlv.n_extra == 0 && wlv.col == win_col_offset) {
const int v = (int)(ptr - line); const int v = (int)(ptr - line);
linenr_T lnume = lnum + foldinfo.fi_lines - 1; linenr_T lnume = lnum + foldinfo.fi_lines - 1;

View File

@ -148,6 +148,11 @@ void conceal_check_cursor_line(void)
} }
redrawWinline(curwin, curwin->w_cursor.lnum); redrawWinline(curwin, curwin->w_cursor.lnum);
// Concealed line visibility toggled.
if (decor_conceal_line(curwin, curwin->w_cursor.lnum - 1, true)) {
changed_window_setting(curwin);
}
// Need to recompute cursor column, e.g., when starting Visual mode // Need to recompute cursor column, e.g., when starting Visual mode
// without concealing. // without concealing.
curs_columns(curwin, true); curs_columns(curwin, true);
@ -1622,7 +1627,7 @@ static void win_update(win_T *wp)
} }
} }
if (mod_top != 0 && hasAnyFolding(wp)) { if (mod_top != 0 && (win_lines_concealed(wp))) {
// A change in a line can cause lines above it to become folded or // A change in a line can cause lines above it to become folded or
// unfolded. Find the top most buffer line that may be affected. // unfolded. Find the top most buffer line that may be affected.
// If the line was previously folded and displayed, get the first // If the line was previously folded and displayed, get the first
@ -1719,12 +1724,12 @@ static void win_update(win_T *wp)
&& wp->w_topfill > wp->w_old_topfill))) { && wp->w_topfill > wp->w_old_topfill))) {
// New topline is above old topline: May scroll down. // New topline is above old topline: May scroll down.
int j; int j;
if (hasAnyFolding(wp)) { if (win_lines_concealed(wp)) {
// count the number of lines we are off, counting a sequence // Count the number of lines we are off, counting a sequence
// of folded lines as one // of folded lines as one, and skip concealed lines.
j = 0; j = 0;
for (linenr_T ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ln++) { for (linenr_T ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ln++) {
j++; j += !decor_conceal_line(wp, ln - 1, false);
if (j >= wp->w_grid.rows - 2) { if (j >= wp->w_grid.rows - 2) {
break; break;
} }
@ -2109,6 +2114,28 @@ static void win_update(win_T *wp)
top_to_mod = false; top_to_mod = false;
} }
// When lines are folded, display one line for all of them.
// Otherwise, display normally (can be several display lines when
// 'wrap' is on).
foldinfo_T foldinfo = wp->w_p_cul && lnum == wp->w_cursor.lnum
? cursorline_fi : fold_info(wp, lnum);
// Concealed line: continue to next line on the same row.
if (decor_conceal_line(wp, lnum - 1, false)) {
if (wp == curwin && lnum == curwin->w_cursor.lnum) {
conceal_cursor_used = conceal_cursor_line(curwin);
}
if (idx > 0) {
wp->w_lines[idx - 1].wl_lastlnum = lnum + foldinfo.fi_lines - 1;
}
if (lnum == mod_top && lnum < mod_bot) {
mod_top += foldinfo.fi_lines ? foldinfo.fi_lines : 1;
}
lnum += foldinfo.fi_lines ? foldinfo.fi_lines : 1;
spv.spv_capcol_lnum = 0;
continue;
}
// When at start of changed lines: May scroll following lines // When at start of changed lines: May scroll following lines
// up or down to minimize redrawing. // up or down to minimize redrawing.
// Don't do this when the change continues until the end. // Don't do this when the change continues until the end.
@ -2158,8 +2185,10 @@ static void win_update(win_T *wp)
// rows, and may insert/delete lines // rows, and may insert/delete lines
int j = idx; int j = idx;
for (l = lnum; l < mod_bot; l++) { for (l = lnum; l < mod_bot; l++) {
linenr_T first = l;
int prev_rows = new_rows;
if (hasFolding(wp, l, NULL, &l)) { if (hasFolding(wp, l, NULL, &l)) {
new_rows++; new_rows += !decor_conceal_line(wp, first - 1, false);
} else if (l == wp->w_topline) { } else if (l == wp->w_topline) {
int n = plines_win_nofill(wp, l, false) + wp->w_topfill int n = plines_win_nofill(wp, l, false) + wp->w_topfill
- adjust_plines_for_skipcol(wp); - adjust_plines_for_skipcol(wp);
@ -2168,7 +2197,8 @@ static void win_update(win_T *wp)
} else { } else {
new_rows += plines_win(wp, l, true); new_rows += plines_win(wp, l, true);
} }
j++; // Do not increment when height was 0 (for a concealed line).
j += (prev_rows != new_rows);
if (new_rows > wp->w_grid.rows - row - 2) { if (new_rows > wp->w_grid.rows - row - 2) {
// it's getting too much, must redraw the rest // it's getting too much, must redraw the rest
new_rows = 9999; new_rows = 9999;
@ -2250,12 +2280,6 @@ static void win_update(win_T *wp)
} }
} }
// When lines are folded, display one line for all of them.
// Otherwise, display normally (can be several display lines when
// 'wrap' is on).
foldinfo_T foldinfo = wp->w_p_cul && lnum == wp->w_cursor.lnum
? cursorline_fi : fold_info(wp, lnum);
if (foldinfo.fi_lines == 0 if (foldinfo.fi_lines == 0
&& idx < wp->w_lines_valid && idx < wp->w_lines_valid
&& wp->w_lines[idx].wl_valid && wp->w_lines[idx].wl_valid
@ -2290,11 +2314,13 @@ static void win_update(win_T *wp)
if (foldinfo.fi_lines == 0) { if (foldinfo.fi_lines == 0) {
wp->w_lines[idx].wl_folded = false; wp->w_lines[idx].wl_folded = false;
wp->w_lines[idx].wl_foldend = lnum;
wp->w_lines[idx].wl_lastlnum = lnum; wp->w_lines[idx].wl_lastlnum = lnum;
did_update = DID_LINE; did_update = DID_LINE;
} else { } else {
foldinfo.fi_lines--; foldinfo.fi_lines--;
wp->w_lines[idx].wl_folded = true; wp->w_lines[idx].wl_folded = true;
wp->w_lines[idx].wl_foldend = lnum + foldinfo.fi_lines;
wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines; wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines;
did_update = DID_FOLD; did_update = DID_FOLD;
} }

View File

@ -15,6 +15,7 @@
#include "nvim/change.h" #include "nvim/change.h"
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/cursor.h" #include "nvim/cursor.h"
#include "nvim/decoration.h"
#include "nvim/digraph.h" #include "nvim/digraph.h"
#include "nvim/drawscreen.h" #include "nvim/drawscreen.h"
#include "nvim/edit.h" #include "nvim/edit.h"
@ -2577,15 +2578,15 @@ int oneleft(void)
return OK; return OK;
} }
/// Move the cursor up "n" lines in window "wp". /// Move the cursor up "n" lines in window "wp". Takes care of closed folds.
/// Takes care of closed folds. /// Skips over concealed lines when "skip_conceal" is true.
void cursor_up_inner(win_T *wp, linenr_T n) void cursor_up_inner(win_T *wp, linenr_T n, bool skip_conceal)
{ {
linenr_T lnum = wp->w_cursor.lnum; linenr_T lnum = wp->w_cursor.lnum;
if (n >= lnum) { if (n >= lnum) {
lnum = 1; lnum = 1;
} else if (hasAnyFolding(wp)) { } else if (win_lines_concealed(wp)) {
// Count each sequence of folded lines as one logical line. // Count each sequence of folded lines as one logical line.
// go to the start of the current fold // go to the start of the current fold
@ -2594,6 +2595,7 @@ void cursor_up_inner(win_T *wp, linenr_T n)
while (n--) { while (n--) {
// move up one line // move up one line
lnum--; lnum--;
n += skip_conceal && decor_conceal_line(wp, lnum - 1, true);
if (lnum <= 1) { if (lnum <= 1) {
break; break;
} }
@ -2619,7 +2621,7 @@ int cursor_up(linenr_T n, bool upd_topline)
if (n > 0 && curwin->w_cursor.lnum <= 1) { if (n > 0 && curwin->w_cursor.lnum <= 1) {
return FAIL; return FAIL;
} }
cursor_up_inner(curwin, n); cursor_up_inner(curwin, n, false);
// try to advance to the column we want to be at // try to advance to the column we want to be at
coladvance(curwin, curwin->w_curswant); coladvance(curwin, curwin->w_curswant);
@ -2631,16 +2633,16 @@ int cursor_up(linenr_T n, bool upd_topline)
return OK; return OK;
} }
/// Move the cursor down "n" lines in window "wp". /// Move the cursor down "n" lines in window "wp". Takes care of closed folds.
/// Takes care of closed folds. /// Skips over concealed lines when "skip_conceal" is true.
void cursor_down_inner(win_T *wp, int n) void cursor_down_inner(win_T *wp, int n, bool skip_conceal)
{ {
linenr_T lnum = wp->w_cursor.lnum; linenr_T lnum = wp->w_cursor.lnum;
linenr_T line_count = wp->w_buffer->b_ml.ml_line_count; linenr_T line_count = wp->w_buffer->b_ml.ml_line_count;
if (lnum + n >= line_count) { if (lnum + n >= line_count) {
lnum = line_count; lnum = line_count;
} else if (hasAnyFolding(wp)) { } else if (win_lines_concealed(wp)) {
linenr_T last; linenr_T last;
// count each sequence of folded lines as one logical line // count each sequence of folded lines as one logical line
@ -2650,6 +2652,7 @@ void cursor_down_inner(win_T *wp, int n)
} else { } else {
lnum++; lnum++;
} }
n += skip_conceal && decor_conceal_line(wp, lnum - 1, true);
if (lnum >= line_count) { if (lnum >= line_count) {
break; break;
} }
@ -2671,7 +2674,7 @@ int cursor_down(int n, bool upd_topline)
if (n > 0 && lnum >= curwin->w_buffer->b_ml.ml_line_count) { if (n > 0 && lnum >= curwin->w_buffer->b_ml.ml_line_count) {
return FAIL; return FAIL;
} }
cursor_down_inner(curwin, n); cursor_down_inner(curwin, n, false);
// try to advance to the column we want to be at // try to advance to the column we want to be at
coladvance(curwin, curwin->w_curswant); coladvance(curwin, curwin->w_curswant);

View File

@ -194,7 +194,7 @@ bool hasFoldingWin(win_T *const win, const linenr_T lnum, linenr_T *const firstp
const int x = find_wl_entry(win, lnum); const int x = find_wl_entry(win, lnum);
if (x >= 0) { if (x >= 0) {
first = win->w_lines[x].wl_lnum; first = win->w_lines[x].wl_lnum;
last = win->w_lines[x].wl_lastlnum; last = win->w_lines[x].wl_foldend;
had_folded = win->w_lines[x].wl_folded; had_folded = win->w_lines[x].wl_folded;
} }
} }
@ -971,7 +971,7 @@ int find_wl_entry(win_T *win, linenr_T lnum)
if (lnum < win->w_lines[i].wl_lnum) { if (lnum < win->w_lines[i].wl_lnum) {
return -1; return -1;
} }
if (lnum <= win->w_lines[i].wl_lastlnum) { if (lnum <= win->w_lines[i].wl_foldend) {
return i; return i;
} }
} }

View File

@ -173,7 +173,7 @@ static const char *highlight_init_both[] = {
"default link PmenuKind Pmenu", "default link PmenuKind Pmenu",
"default link PmenuKindSel PmenuSel", "default link PmenuKindSel PmenuSel",
"default link PmenuSbar Pmenu", "default link PmenuSbar Pmenu",
"default link ComplMatchIns Normal", "default link ComplMatchIns NONE",
"default link Substitute Search", "default link Substitute Search",
"default link StatusLineTerm StatusLine", "default link StatusLineTerm StatusLine",
"default link StatusLineTermNC StatusLineNC", "default link StatusLineTermNC StatusLineNC",

View File

@ -958,7 +958,7 @@ static void ins_compl_insert_bytes(char *p, int len)
} }
assert(len >= 0); assert(len >= 0);
ins_bytes_len(p, (size_t)len); ins_bytes_len(p, (size_t)len);
compl_ins_end_col = curwin->w_cursor.col - 1; compl_ins_end_col = curwin->w_cursor.col;
} }
/// Checks if the column is within the currently inserted completion text /// Checks if the column is within the currently inserted completion text
@ -2147,6 +2147,8 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval)
&& pum_visible()) { && pum_visible()) {
word = xstrdup(compl_shown_match->cp_str); word = xstrdup(compl_shown_match->cp_str);
retval = true; retval = true;
// May need to remove ComplMatchIns highlight.
redrawWinline(curwin, curwin->w_cursor.lnum);
} }
// CTRL-E means completion is Ended, go back to the typed text. // CTRL-E means completion is Ended, go back to the typed text.
@ -3648,6 +3650,7 @@ void ins_compl_delete(bool new_leader)
return; return;
} }
backspace_until_column(col); backspace_until_column(col);
compl_ins_end_col = curwin->w_cursor.col;
} }
// TODO(vim): is this sufficient for redrawing? Redrawing everything // TODO(vim): is this sufficient for redrawing? Redrawing everything

View File

@ -250,7 +250,7 @@ static inline void split_node(MarkTree *b, MTNode *x, const int i, MTKey next)
refkey(b, x, i); refkey(b, x, i);
x->n++; x->n++;
uint32_t meta_inc[4]; uint32_t meta_inc[kMTMetaCount];
meta_describe_key(meta_inc, x->key[i]); meta_describe_key(meta_inc, x->key[i]);
for (int m = 0; m < kMTMetaCount; m++) { for (int m = 0; m < kMTMetaCount; m++) {
// y used contain all of z and x->key[i], discount those // y used contain all of z and x->key[i], discount those
@ -451,6 +451,7 @@ static void meta_describe_key_inc(uint32_t *meta_inc, MTKey *k)
meta_inc[kMTMetaLines] += (k->flags & MT_FLAG_DECOR_VIRT_LINES) ? 1 : 0; meta_inc[kMTMetaLines] += (k->flags & MT_FLAG_DECOR_VIRT_LINES) ? 1 : 0;
meta_inc[kMTMetaSignHL] += (k->flags & MT_FLAG_DECOR_SIGNHL) ? 1 : 0; meta_inc[kMTMetaSignHL] += (k->flags & MT_FLAG_DECOR_SIGNHL) ? 1 : 0;
meta_inc[kMTMetaSignText] += (k->flags & MT_FLAG_DECOR_SIGNTEXT) ? 1 : 0; meta_inc[kMTMetaSignText] += (k->flags & MT_FLAG_DECOR_SIGNTEXT) ? 1 : 0;
meta_inc[kMTMetaConcealLines] += (k->flags & MT_FLAG_DECOR_CONCEAL_LINES) ? 1 : 0;
} }
} }
@ -505,10 +506,10 @@ void marktree_put_key(MarkTree *b, MTKey k)
r = s; r = s;
} }
uint32_t meta_inc[4]; uint32_t meta_inc[kMTMetaCount];
meta_describe_key(meta_inc, k); meta_describe_key(meta_inc, k);
marktree_putp_aux(b, r, k, meta_inc); marktree_putp_aux(b, r, k, meta_inc);
for (int m = 0; m < 4; m++) { for (int m = 0; m < kMTMetaCount; m++) {
b->meta_root[m] += meta_inc[m]; b->meta_root[m] += meta_inc[m];
} }
b->n_keys++; b->n_keys++;
@ -579,7 +580,7 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
assert(x->level == 0); assert(x->level == 0);
MTKey intkey = x->key[itr->i]; MTKey intkey = x->key[itr->i];
uint32_t meta_inc[4]; uint32_t meta_inc[kMTMetaCount];
meta_describe_key(meta_inc, intkey); meta_describe_key(meta_inc, intkey);
if (x->n > itr->i + 1) { if (x->n > itr->i + 1) {
memmove(&x->key[itr->i], &x->key[itr->i + 1], memmove(&x->key[itr->i], &x->key[itr->i + 1],
@ -776,7 +777,7 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
void marktree_revise_meta(MarkTree *b, MarkTreeIter *itr, MTKey old_key) void marktree_revise_meta(MarkTree *b, MarkTreeIter *itr, MTKey old_key)
{ {
uint32_t meta_old[4], meta_new[4]; uint32_t meta_old[kMTMetaCount], meta_new[kMTMetaCount];
meta_describe_key(meta_old, old_key); meta_describe_key(meta_old, old_key);
meta_describe_key(meta_new, rawkey(itr)); meta_describe_key(meta_new, rawkey(itr));
@ -1038,7 +1039,7 @@ static MTNode *merge_node(MarkTree *b, MTNode *p, int i)
relative(p->key[i - 1].pos, &x->key[x->n].pos); relative(p->key[i - 1].pos, &x->key[x->n].pos);
} }
uint32_t meta_inc[4]; uint32_t meta_inc[kMTMetaCount];
meta_describe_key(meta_inc, x->key[x->n]); meta_describe_key(meta_inc, x->key[x->n]);
memmove(&x->key[x->n + 1], y->key, (size_t)y->n * sizeof(MTKey)); memmove(&x->key[x->n + 1], y->key, (size_t)y->n * sizeof(MTKey));
@ -1128,9 +1129,9 @@ static void pivot_right(MarkTree *b, MTPos p_pos, MTNode *p, const int i)
p->key[i] = x->key[x->n - 1]; p->key[i] = x->key[x->n - 1];
refkey(b, p, i); refkey(b, p, i);
uint32_t meta_inc_y[4]; uint32_t meta_inc_y[kMTMetaCount];
meta_describe_key(meta_inc_y, y->key[0]); meta_describe_key(meta_inc_y, y->key[0]);
uint32_t meta_inc_x[4]; uint32_t meta_inc_x[kMTMetaCount];
meta_describe_key(meta_inc_x, p->key[i]); meta_describe_key(meta_inc_x, p->key[i]);
for (int m = 0; m < kMTMetaCount; m++) { for (int m = 0; m < kMTMetaCount; m++) {
@ -1214,9 +1215,9 @@ static void pivot_left(MarkTree *b, MTPos p_pos, MTNode *p, int i)
p->key[i] = y->key[0]; p->key[i] = y->key[0];
refkey(b, p, i); refkey(b, p, i);
uint32_t meta_inc_x[4]; uint32_t meta_inc_x[kMTMetaCount];
meta_describe_key(meta_inc_x, x->key[x->n]); meta_describe_key(meta_inc_x, x->key[x->n]);
uint32_t meta_inc_y[4]; uint32_t meta_inc_y[kMTMetaCount];
meta_describe_key(meta_inc_y, p->key[i]); meta_describe_key(meta_inc_y, p->key[i]);
for (int m = 0; m < kMTMetaCount; m++) { for (int m = 0; m < kMTMetaCount; m++) {
p->meta[i][m] += meta_inc_x[m]; p->meta[i][m] += meta_inc_x[m];
@ -1619,8 +1620,13 @@ bool marktree_itr_next_filter(MarkTree *b, MarkTreeIter *itr, int stop_row, int
return marktree_itr_check_filter(b, itr, stop_row, stop_col, meta_filter); return marktree_itr_check_filter(b, itr, stop_row, stop_col, meta_filter);
} }
const uint32_t meta_map[4] = { MT_FLAG_DECOR_VIRT_TEXT_INLINE, MT_FLAG_DECOR_VIRT_LINES, const uint32_t meta_map[kMTMetaCount] = {
MT_FLAG_DECOR_SIGNHL, MT_FLAG_DECOR_SIGNTEXT }; MT_FLAG_DECOR_VIRT_TEXT_INLINE,
MT_FLAG_DECOR_VIRT_LINES,
MT_FLAG_DECOR_SIGNHL,
MT_FLAG_DECOR_SIGNTEXT,
MT_FLAG_DECOR_CONCEAL_LINES
};
static bool marktree_itr_check_filter(MarkTree *b, MarkTreeIter *itr, int stop_row, int stop_col, static bool marktree_itr_check_filter(MarkTree *b, MarkTreeIter *itr, int stop_row, int stop_col,
MetaFilter meta_filter) MetaFilter meta_filter)
{ {
@ -1860,9 +1866,9 @@ static void swap_keys(MarkTree *b, MarkTreeIter *itr1, MarkTreeIter *itr2, Damag
itr2->i, itr1->i })); itr2->i, itr1->i }));
} }
uint32_t meta_inc_1[4]; uint32_t meta_inc_1[kMTMetaCount];
meta_describe_key(meta_inc_1, rawkey(itr1)); meta_describe_key(meta_inc_1, rawkey(itr1));
uint32_t meta_inc_2[4]; uint32_t meta_inc_2[kMTMetaCount];
meta_describe_key(meta_inc_2, rawkey(itr2)); meta_describe_key(meta_inc_2, rawkey(itr2));
if (memcmp(meta_inc_1, meta_inc_2, sizeof(meta_inc_1)) != 0) { if (memcmp(meta_inc_1, meta_inc_2, sizeof(meta_inc_1)) != 0) {
@ -2373,7 +2379,7 @@ size_t marktree_check_node(MarkTree *b, MTNode *x, MTPos *last, bool *last_right
*last = x->key[x->n - 1].pos; *last = x->key[x->n - 1].pos;
} }
uint32_t meta_node[4]; uint32_t meta_node[kMTMetaCount];
meta_describe_node(meta_node, x); meta_describe_node(meta_node, x);
for (int m = 0; m < kMTMetaCount; m++) { for (int m = 0; m < kMTMetaCount; m++) {
assert(meta_node_ref[m] == meta_node[m]); assert(meta_node_ref[m] == meta_node[m]);

View File

@ -34,6 +34,7 @@
#define MT_FLAG_DECOR_SIGNHL (((uint16_t)1) << 10) #define MT_FLAG_DECOR_SIGNHL (((uint16_t)1) << 10)
#define MT_FLAG_DECOR_VIRT_LINES (((uint16_t)1) << 11) #define MT_FLAG_DECOR_VIRT_LINES (((uint16_t)1) << 11)
#define MT_FLAG_DECOR_VIRT_TEXT_INLINE (((uint16_t)1) << 12) #define MT_FLAG_DECOR_VIRT_TEXT_INLINE (((uint16_t)1) << 12)
#define MT_FLAG_DECOR_CONCEAL_LINES (((uint16_t)1) << 13)
// These _must_ be last to preserve ordering of marks // These _must_ be last to preserve ordering of marks
#define MT_FLAG_RIGHT_GRAVITY (((uint16_t)1) << 14) #define MT_FLAG_RIGHT_GRAVITY (((uint16_t)1) << 14)
@ -43,8 +44,8 @@
| MT_FLAG_DECOR_SIGNHL | MT_FLAG_DECOR_VIRT_LINES \ | MT_FLAG_DECOR_SIGNHL | MT_FLAG_DECOR_VIRT_LINES \
| MT_FLAG_DECOR_VIRT_TEXT_INLINE) | MT_FLAG_DECOR_VIRT_TEXT_INLINE)
#define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_NO_UNDO \ #define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_NO_UNDO | MT_FLAG_INVALIDATE \
| MT_FLAG_INVALIDATE | MT_FLAG_INVALID) | MT_FLAG_INVALID | MT_FLAG_DECOR_CONCEAL_LINES)
// this is defined so that start and end of the same range have adjacent ids // this is defined so that start and end of the same range have adjacent ids
#define MARKTREE_END_FLAG ((uint64_t)1) #define MARKTREE_END_FLAG ((uint64_t)1)
@ -108,6 +109,11 @@ static inline bool mt_decor_sign(MTKey key)
return key.flags & (MT_FLAG_DECOR_SIGNTEXT | MT_FLAG_DECOR_SIGNHL); return key.flags & (MT_FLAG_DECOR_SIGNTEXT | MT_FLAG_DECOR_SIGNHL);
} }
static inline bool mt_conceal_lines(MTKey key)
{
return key.flags & MT_FLAG_DECOR_CONCEAL_LINES;
}
static inline uint16_t mt_flags(bool right_gravity, bool no_undo, bool invalidate, bool decor_ext) static inline uint16_t mt_flags(bool right_gravity, bool no_undo, bool invalidate, bool decor_ext)
{ {
return (uint16_t)((right_gravity ? MT_FLAG_RIGHT_GRAVITY : 0) return (uint16_t)((right_gravity ? MT_FLAG_RIGHT_GRAVITY : 0)

View File

@ -22,13 +22,12 @@ typedef struct {
} MTPos; } MTPos;
#define MTPos(r, c) ((MTPos){ .row = (r), .col = (c) }) #define MTPos(r, c) ((MTPos){ .row = (r), .col = (c) })
// Currently there are four counts, which makes for a uint32_t[4] per node
// which makes for nice autovectorization into a single XMM or NEON register
typedef enum { typedef enum {
kMTMetaInline, kMTMetaInline,
kMTMetaLines, kMTMetaLines,
kMTMetaSignHL, kMTMetaSignHL,
kMTMetaSignText, kMTMetaSignText,
kMTMetaConcealLines,
kMTMetaCount, // sentinel, must be last kMTMetaCount, // sentinel, must be last
} MetaIndex; } MetaIndex;

View File

@ -17,6 +17,7 @@
#include "nvim/buffer.h" #include "nvim/buffer.h"
#include "nvim/buffer_defs.h" #include "nvim/buffer_defs.h"
#include "nvim/cursor.h" #include "nvim/cursor.h"
#include "nvim/decoration.h"
#include "nvim/diff.h" #include "nvim/diff.h"
#include "nvim/drawscreen.h" #include "nvim/drawscreen.h"
#include "nvim/edit.h" #include "nvim/edit.h"
@ -318,14 +319,13 @@ void update_topline(win_T *wp)
halfheight = 2; halfheight = 2;
} }
int64_t n; int64_t n;
if (hasAnyFolding(wp)) { if (win_lines_concealed(wp)) {
// Count the number of logical lines between the cursor and // Count the number of logical lines between the cursor and
// topline + p_so (approximation of how much will be // topline + p_so (approximation of how much will be
// scrolled). // scrolled).
n = 0; n = 0;
for (linenr_T lnum = wp->w_cursor.lnum; for (linenr_T lnum = wp->w_cursor.lnum; lnum < wp->w_topline + *so_ptr; lnum++) {
lnum < wp->w_topline + *so_ptr; lnum++) { n += !decor_conceal_line(wp, lnum, false);
n++;
// stop at end of file or when we know we are far off // stop at end of file or when we know we are far off
assert(wp->w_buffer != 0); assert(wp->w_buffer != 0);
if (lnum >= wp->w_buffer->b_ml.ml_line_count || n >= halfheight) { if (lnum >= wp->w_buffer->b_ml.ml_line_count || n >= halfheight) {
@ -367,7 +367,7 @@ void update_topline(win_T *wp)
assert(wp->w_buffer != 0); assert(wp->w_buffer != 0);
if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) { if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) {
if (wp->w_cursor.lnum < wp->w_botline) { if (wp->w_cursor.lnum < wp->w_botline) {
if ((wp->w_cursor.lnum >= wp->w_botline - *so_ptr || hasAnyFolding(wp))) { if ((wp->w_cursor.lnum >= wp->w_botline - *so_ptr || win_lines_concealed(wp))) {
lineoff_T loff; lineoff_T loff;
// Cursor is (a few lines) above botline, check if there are // Cursor is (a few lines) above botline, check if there are
@ -399,13 +399,12 @@ void update_topline(win_T *wp)
} }
if (check_botline) { if (check_botline) {
int line_count = 0; int line_count = 0;
if (hasAnyFolding(wp)) { if (win_lines_concealed(wp)) {
// Count the number of logical lines between the cursor and // Count the number of logical lines between the cursor and
// botline - p_so (approximation of how much will be // botline - p_so (approximation of how much will be
// scrolled). // scrolled).
for (linenr_T lnum = wp->w_cursor.lnum; for (linenr_T lnum = wp->w_cursor.lnum; lnum >= wp->w_botline - *so_ptr; lnum--) {
lnum >= wp->w_botline - *so_ptr; lnum--) { line_count += !decor_conceal_line(wp, lnum - 1, false);
line_count++;
// stop at end of file or when we know we are far off // stop at end of file or when we know we are far off
if (lnum <= 0 || line_count > wp->w_height_inner + 1) { if (lnum <= 0 || line_count > wp->w_height_inner + 1) {
break; break;
@ -462,7 +461,7 @@ static int scrolljump_value(win_T *wp)
static bool check_top_offset(win_T *wp) static bool check_top_offset(win_T *wp)
{ {
int so = get_scrolloff_value(wp); int so = get_scrolloff_value(wp);
if (wp->w_cursor.lnum < wp->w_topline + so || hasAnyFolding(wp)) { if (wp->w_cursor.lnum < wp->w_topline + so || win_lines_concealed(wp)) {
lineoff_T loff; lineoff_T loff;
loff.lnum = wp->w_cursor.lnum; loff.lnum = wp->w_cursor.lnum;
loff.fill = 0; loff.fill = 0;
@ -506,6 +505,13 @@ void check_cursor_moved(win_T *wp)
if (wp->w_cursor.lnum != wp->w_valid_cursor.lnum) { if (wp->w_cursor.lnum != wp->w_valid_cursor.lnum) {
wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
|VALID_CHEIGHT|VALID_CROW|VALID_TOPLINE); |VALID_CHEIGHT|VALID_CROW|VALID_TOPLINE);
// Concealed line visibility toggled.
if (wp->w_p_cole >= 2 && !conceal_cursor_line(wp)
&& (decor_conceal_line(wp, wp->w_cursor.lnum - 1, true)
|| decor_conceal_line(wp, wp->w_valid_cursor.lnum - 1, true))) {
changed_window_setting(wp);
}
wp->w_valid_cursor = wp->w_cursor; wp->w_valid_cursor = wp->w_cursor;
wp->w_valid_leftcol = wp->w_leftcol; wp->w_valid_leftcol = wp->w_leftcol;
wp->w_valid_skipcol = wp->w_skipcol; wp->w_valid_skipcol = wp->w_skipcol;
@ -1341,13 +1347,14 @@ bool scrolldown(win_T *wp, linenr_T line_count, int byfold)
// A sequence of folded lines only counts for one logical line // A sequence of folded lines only counts for one logical line
linenr_T first; linenr_T first;
if (hasFolding(wp, wp->w_topline, &first, NULL)) { if (hasFolding(wp, wp->w_topline, &first, NULL)) {
done++; done += !decor_conceal_line(wp, first - 1, false);
if (!byfold) { if (!byfold) {
todo -= wp->w_topline - first - 1; todo -= wp->w_topline - first - 1;
} }
wp->w_botline -= wp->w_topline - first; wp->w_botline -= wp->w_topline - first;
wp->w_topline = first; wp->w_topline = first;
} else { } else {
todo += decor_conceal_line(wp, wp->w_topline - 1, false);
if (do_sms) { if (do_sms) {
int size = win_linetabsize(wp, wp->w_topline, int size = win_linetabsize(wp, wp->w_topline,
ml_get_buf(wp->w_buffer, wp->w_topline), MAXCOL); ml_get_buf(wp->w_buffer, wp->w_topline), MAXCOL);
@ -1391,12 +1398,8 @@ bool scrolldown(win_T *wp, linenr_T line_count, int byfold)
while (wrow >= wp->w_height_inner && wp->w_cursor.lnum > 1) { while (wrow >= wp->w_height_inner && wp->w_cursor.lnum > 1) {
linenr_T first; linenr_T first;
if (hasFolding(wp, wp->w_cursor.lnum, &first, NULL)) { if (hasFolding(wp, wp->w_cursor.lnum, &first, NULL)) {
wrow--; wrow -= !decor_conceal_line(wp, wp->w_cursor.lnum - 1, false);
if (first == 1) { wp->w_cursor.lnum = MAX(first - 1, 1);
wp->w_cursor.lnum = 1;
} else {
wp->w_cursor.lnum = first - 1;
}
} else { } else {
wrow -= plines_win(wp, wp->w_cursor.lnum--, true); wrow -= plines_win(wp, wp->w_cursor.lnum--, true);
} }
@ -1424,7 +1427,7 @@ bool scrollup(win_T *wp, linenr_T line_count, bool byfold)
linenr_T botline = wp->w_botline; linenr_T botline = wp->w_botline;
bool do_sms = wp->w_p_wrap && wp->w_p_sms; bool do_sms = wp->w_p_wrap && wp->w_p_sms;
if (do_sms || (byfold && hasAnyFolding(wp)) || win_may_fill(wp)) { if (do_sms || (byfold && win_lines_concealed(wp)) || win_may_fill(wp)) {
int width1 = wp->w_width_inner - win_col_off(wp); int width1 = wp->w_width_inner - win_col_off(wp);
int width2 = width1 + win_col_off2(wp); int width2 = width1 + win_col_off2(wp);
int size = 0; int size = 0;
@ -1439,6 +1442,7 @@ bool scrollup(win_T *wp, linenr_T line_count, bool byfold)
// the line, then advance to the next line. // the line, then advance to the next line.
// folding: count each sequence of folded lines as one logical line. // folding: count each sequence of folded lines as one logical line.
for (int todo = line_count; todo > 0; todo--) { for (int todo = line_count; todo > 0; todo--) {
todo += decor_conceal_line(wp, wp->w_topline - 1, false);
if (wp->w_topfill > 0) { if (wp->w_topfill > 0) {
wp->w_topfill--; wp->w_topfill--;
} else { } else {
@ -1495,10 +1499,8 @@ bool scrollup(win_T *wp, linenr_T line_count, bool byfold)
check_topfill(wp, false); check_topfill(wp, false);
if (hasAnyFolding(wp)) { // Make sure w_topline is at the first of a sequence of folded lines.
// Make sure w_topline is at the first of a sequence of folded lines. hasFolding(wp, wp->w_topline, &wp->w_topline, NULL);
hasFolding(wp, wp->w_topline, &wp->w_topline, NULL);
}
wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE); wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
if (wp->w_cursor.lnum < wp->w_topline) { if (wp->w_cursor.lnum < wp->w_topline) {
@ -1702,8 +1704,8 @@ static void topline_back_winheight(win_T *wp, lineoff_T *lp, int winheight)
if (lp->lnum < 1) { if (lp->lnum < 1) {
lp->height = MAXCOL; lp->height = MAXCOL;
} else if (hasFolding(wp, lp->lnum, &lp->lnum, NULL)) { } else if (hasFolding(wp, lp->lnum, &lp->lnum, NULL)) {
// Add a closed fold // Add a closed fold unless concealed.
lp->height = 1; lp->height = !decor_conceal_line(wp, lp->lnum - 1, false);
} else { } else {
lp->height = plines_win_nofill(wp, lp->lnum, winheight); lp->height = plines_win_nofill(wp, lp->lnum, winheight);
} }
@ -1732,8 +1734,8 @@ static void botline_forw(win_T *wp, lineoff_T *lp)
if (lp->lnum > wp->w_buffer->b_ml.ml_line_count) { if (lp->lnum > wp->w_buffer->b_ml.ml_line_count) {
lp->height = MAXCOL; lp->height = MAXCOL;
} else if (hasFolding(wp, lp->lnum, NULL, &lp->lnum)) { } else if (hasFolding(wp, lp->lnum, NULL, &lp->lnum)) {
// Add a closed fold // Add a closed fold unless concealed.
lp->height = 1; lp->height = !decor_conceal_line(wp, lp->lnum - 1, false);
} else { } else {
lp->height = plines_win_nofill(wp, lp->lnum, true); lp->height = plines_win_nofill(wp, lp->lnum, true);
} }
@ -1785,9 +1787,8 @@ void scroll_cursor_top(win_T *wp, int min_scroll, int always)
// Check if the lines from "top" to "bot" fit in the window. If they do, // Check if the lines from "top" to "bot" fit in the window. If they do,
// set new_topline and advance "top" and "bot" to include more lines. // set new_topline and advance "top" and "bot" to include more lines.
while (top > 0) { while (top > 0) {
int i = hasFolding(wp, top, &top, NULL) int i = plines_win_nofill(wp, top, true);
? 1 // count one logical line for a sequence of folded lines hasFolding(wp, top, &top, NULL);
: plines_win_nofill(wp, top, true);
if (top < wp->w_topline) { if (top < wp->w_topline) {
scrolled += i; scrolled += i;
} }
@ -1799,12 +1800,7 @@ void scroll_cursor_top(win_T *wp, int min_scroll, int always)
used += i; used += i;
if (extra + i <= off && bot < wp->w_buffer->b_ml.ml_line_count) { if (extra + i <= off && bot < wp->w_buffer->b_ml.ml_line_count) {
if (hasFolding(wp, bot, NULL, &bot)) { used += plines_win_full(wp, bot, &bot, NULL, true, true);
// count one logical line for a sequence of folded lines
used++;
} else {
used += plines_win(wp, bot, true);
}
} }
if (used > wp->w_height_inner) { if (used > wp->w_height_inner) {
break; break;
@ -2265,7 +2261,7 @@ void cursor_correct(win_T *wp)
linenr_T cln = wp->w_cursor.lnum; // Cursor Line Number linenr_T cln = wp->w_cursor.lnum; // Cursor Line Number
if (cln >= wp->w_topline + above_wanted if (cln >= wp->w_topline + above_wanted
&& cln < wp->w_botline - below_wanted && cln < wp->w_botline - below_wanted
&& !hasAnyFolding(wp)) { && !win_lines_concealed(wp)) {
return; return;
} }
@ -2290,19 +2286,12 @@ void cursor_correct(win_T *wp)
int below = wp->w_filler_rows; // screen lines below botline int below = wp->w_filler_rows; // screen lines below botline
while ((above < above_wanted || below < below_wanted) && topline < botline) { while ((above < above_wanted || below < below_wanted) && topline < botline) {
if (below < below_wanted && (below <= above || above >= above_wanted)) { if (below < below_wanted && (below <= above || above >= above_wanted)) {
if (hasFolding(wp, botline, &botline, NULL)) { below += plines_win_full(wp, botline, &botline, NULL, true, true);
below++;
} else {
below += plines_win(wp, botline, true);
}
botline--; botline--;
} }
if (above < above_wanted && (above < below || below >= below_wanted)) { if (above < above_wanted && (above < below || below >= below_wanted)) {
if (hasFolding(wp, topline, NULL, &topline)) { above += plines_win_nofill(wp, topline, true);
above++; hasFolding(wp, topline, NULL, &topline);
} else {
above += plines_win_nofill(wp, topline, true);
}
// Count filler lines below this line as context. // Count filler lines below this line as context.
if (topline < botline) { if (topline < botline) {
@ -2456,7 +2445,8 @@ int pagescroll(Direction dir, int count, bool half)
int curscount = count; int curscount = count;
// Adjust count so as to not reveal end of buffer lines. // Adjust count so as to not reveal end of buffer lines.
if (dir == FORWARD if (dir == FORWARD
&& (curwin->w_topline + curwin->w_height_inner + count > buflen || hasAnyFolding(curwin))) { && (curwin->w_topline + curwin->w_height_inner + count > buflen
|| win_lines_concealed(curwin))) {
int n = plines_correct_topline(curwin, curwin->w_topline, NULL, false, NULL); int n = plines_correct_topline(curwin, curwin->w_topline, NULL, false, NULL);
if (n - count < curwin->w_height_inner && curwin->w_topline < buflen) { if (n - count < curwin->w_height_inner && curwin->w_topline < buflen) {
n += plines_m_win(curwin, curwin->w_topline + 1, buflen, curwin->w_height_inner + count); n += plines_m_win(curwin, curwin->w_topline + 1, buflen, curwin->w_height_inner + count);
@ -2474,13 +2464,14 @@ int pagescroll(Direction dir, int count, bool half)
curwin->w_curswant = prev_curswant; curwin->w_curswant = prev_curswant;
} }
// Move the cursor the same amount of screen lines. // Move the cursor the same amount of screen lines, skipping over
// concealed lines as those were not included in "curscount".
if (curwin->w_p_wrap) { if (curwin->w_p_wrap) {
nv_screengo(&oa, dir, curscount); nv_screengo(&oa, dir, curscount, true);
} else if (dir == FORWARD) { } else if (dir == FORWARD) {
cursor_down_inner(curwin, curscount); cursor_down_inner(curwin, curscount, true);
} else { } else {
cursor_up_inner(curwin, curscount); cursor_up_inner(curwin, curscount, true);
} }
} else { } else {
// Scroll [count] times 'window' or current window height lines. // Scroll [count] times 'window' or current window height lines.

View File

@ -24,6 +24,7 @@
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/cmdhist.h" #include "nvim/cmdhist.h"
#include "nvim/cursor.h" #include "nvim/cursor.h"
#include "nvim/decoration.h"
#include "nvim/diff.h" #include "nvim/diff.h"
#include "nvim/digraph.h" #include "nvim/digraph.h"
#include "nvim/drawscreen.h" #include "nvim/drawscreen.h"
@ -2486,7 +2487,7 @@ bool find_decl(char *ptr, size_t len, bool locally, bool thisblock, int flags_ar
/// 'dist' must be positive. /// 'dist' must be positive.
/// ///
/// @return true if able to move cursor, false otherwise. /// @return true if able to move cursor, false otherwise.
bool nv_screengo(oparg_T *oap, int dir, int dist) bool nv_screengo(oparg_T *oap, int dir, int dist, bool skip_conceal)
{ {
int linelen = linetabsize(curwin, curwin->w_cursor.lnum); int linelen = linetabsize(curwin, curwin->w_cursor.lnum);
bool retval = true; bool retval = true;
@ -2548,7 +2549,7 @@ bool nv_screengo(oparg_T *oap, int dir, int dist)
retval = false; retval = false;
break; break;
} }
cursor_up_inner(curwin, 1); cursor_up_inner(curwin, 1, skip_conceal);
linelen = linetabsize(curwin, curwin->w_cursor.lnum); linelen = linetabsize(curwin, curwin->w_cursor.lnum);
if (linelen > width1) { if (linelen > width1) {
@ -2573,7 +2574,7 @@ bool nv_screengo(oparg_T *oap, int dir, int dist)
retval = false; retval = false;
break; break;
} }
cursor_down_inner(curwin, 1); cursor_down_inner(curwin, 1, skip_conceal);
curwin->w_curswant %= width2; curwin->w_curswant %= width2;
// Check if the cursor has moved below the number display // Check if the cursor has moved below the number display
@ -3616,12 +3617,11 @@ static void nv_scroll(cmdarg_T *cap)
if (cap->count1 - 1 >= curwin->w_cursor.lnum) { if (cap->count1 - 1 >= curwin->w_cursor.lnum) {
curwin->w_cursor.lnum = 1; curwin->w_cursor.lnum = 1;
} else { } else {
if (hasAnyFolding(curwin)) { if (win_lines_concealed(curwin)) {
// Count a fold for one screen line. // Count a fold for one screen line.
for (n = cap->count1 - 1; n > 0 for (n = cap->count1 - 1; n > 0 && curwin->w_cursor.lnum > curwin->w_topline; n--) {
&& curwin->w_cursor.lnum > curwin->w_topline; n--) { hasFolding(curwin, curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL);
hasFolding(curwin, curwin->w_cursor.lnum, n += decor_conceal_line(curwin, curwin->w_cursor.lnum, true);
&curwin->w_cursor.lnum, NULL);
if (curwin->w_cursor.lnum > curwin->w_topline) { if (curwin->w_cursor.lnum > curwin->w_topline) {
curwin->w_cursor.lnum--; curwin->w_cursor.lnum--;
} }
@ -3634,8 +3634,7 @@ static void nv_scroll(cmdarg_T *cap)
if (cap->cmdchar == 'M') { if (cap->cmdchar == 'M') {
int used = 0; int used = 0;
// Don't count filler lines above the window. // Don't count filler lines above the window.
used -= win_get_fill(curwin, curwin->w_topline) used -= win_get_fill(curwin, curwin->w_topline) - curwin->w_topfill;
- curwin->w_topfill;
validate_botline(curwin); // make sure w_empty_rows is valid validate_botline(curwin); // make sure w_empty_rows is valid
int half = (curwin->w_height_inner - curwin->w_empty_rows + 1) / 2; int half = (curwin->w_height_inner - curwin->w_empty_rows + 1) / 2;
for (n = 0; curwin->w_topline + n < curbuf->b_ml.ml_line_count; n++) { for (n = 0; curwin->w_topline + n < curbuf->b_ml.ml_line_count; n++) {
@ -3658,10 +3657,11 @@ static void nv_scroll(cmdarg_T *cap)
} }
} else { // (cap->cmdchar == 'H') } else { // (cap->cmdchar == 'H')
n = cap->count1 - 1; n = cap->count1 - 1;
if (hasAnyFolding(curwin)) { if (win_lines_concealed(curwin)) {
// Count a fold for one screen line. // Count a fold for one screen line.
lnum = curwin->w_topline; lnum = curwin->w_topline;
while (n-- > 0 && lnum < curwin->w_botline - 1) { while ((decor_conceal_line(curwin, lnum - 1, true) || n-- > 0)
&& lnum < curwin->w_botline - 1) {
hasFolding(curwin, lnum, NULL, &lnum); hasFolding(curwin, lnum, NULL, &lnum);
lnum++; lnum++;
} }
@ -5316,7 +5316,7 @@ static void nv_g_dollar_cmd(cmdarg_T *cap)
curwin->w_cursor.col--; curwin->w_cursor.col--;
} }
} }
} else if (nv_screengo(oap, FORWARD, cap->count1 - 1) == false) { } else if (nv_screengo(oap, FORWARD, cap->count1 - 1, false) == false) {
clearopbeep(oap); clearopbeep(oap);
} }
} else { } else {
@ -5443,7 +5443,7 @@ static void nv_g_cmd(cmdarg_T *cap)
oap->motion_type = kMTLineWise; oap->motion_type = kMTLineWise;
i = cursor_down(cap->count1, oap->op_type == OP_NOP); i = cursor_down(cap->count1, oap->op_type == OP_NOP);
} else { } else {
i = nv_screengo(oap, FORWARD, cap->count1); i = nv_screengo(oap, FORWARD, cap->count1, false);
} }
if (!i) { if (!i) {
clearopbeep(oap); clearopbeep(oap);
@ -5457,7 +5457,7 @@ static void nv_g_cmd(cmdarg_T *cap)
oap->motion_type = kMTLineWise; oap->motion_type = kMTLineWise;
i = cursor_up(cap->count1, oap->op_type == OP_NOP); i = cursor_up(cap->count1, oap->op_type == OP_NOP);
} else { } else {
i = nv_screengo(oap, BACKWARD, cap->count1); i = nv_screengo(oap, BACKWARD, cap->count1, false);
} }
if (!i) { if (!i) {
clearopbeep(oap); clearopbeep(oap);

View File

@ -80,7 +80,7 @@ int linetabsize(win_T *wp, linenr_T lnum)
return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), MAXCOL); return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), MAXCOL);
} }
static const uint32_t inline_filter[4] = {[kMTMetaInline] = kMTFilterSelect }; static const uint32_t inline_filter[kMTMetaCount] = {[kMTMetaInline] = kMTFilterSelect };
/// Prepare the structure passed to charsize functions. /// Prepare the structure passed to charsize functions.
/// ///
@ -749,6 +749,10 @@ int plines_win(win_T *wp, linenr_T lnum, bool limit_winheight)
/// @param limit_winheight when true limit to window height /// @param limit_winheight when true limit to window height
int plines_win_nofill(win_T *wp, linenr_T lnum, bool limit_winheight) int plines_win_nofill(win_T *wp, linenr_T lnum, bool limit_winheight)
{ {
if (decor_conceal_line(wp, lnum - 1, false)) {
return 0;
}
if (!wp->w_p_wrap) { if (!wp->w_p_wrap) {
return 1; return 1;
} }
@ -885,6 +889,11 @@ int plines_win_full(win_T *wp, linenr_T lnum, linenr_T *const nextp, bool *const
if (foldedp != NULL) { if (foldedp != NULL) {
*foldedp = folded; *foldedp = folded;
} }
if (decor_conceal_line(wp, lnum - 1, false)) {
return 0;
}
return ((folded ? 1 : plines_win_nofill(wp, lnum, limit_winheight)) + return ((folded ? 1 : plines_win_nofill(wp, lnum, limit_winheight)) +
(lnum == wp->w_topline ? wp->w_topfill : win_get_fill(wp, lnum))); (lnum == wp->w_topline ? wp->w_topfill : win_get_fill(wp, lnum)));
} }
@ -963,8 +972,8 @@ int64_t win_text_height(win_T *const wp, const linenr_T start_lnum, const int64_
if (start_vcol >= 0) { if (start_vcol >= 0) {
linenr_T lnum_next = lnum; linenr_T lnum_next = lnum;
const bool folded = hasFolding(wp, lnum, &lnum, &lnum_next); hasFolding(wp, lnum, &lnum, &lnum_next);
height_cur_nofill = folded ? 1 : plines_win_nofill(wp, lnum, false); height_cur_nofill = plines_win_nofill(wp, lnum, false);
height_sum_nofill += height_cur_nofill; height_sum_nofill += height_cur_nofill;
const int64_t row_off = (start_vcol < width1 || width2 <= 0) const int64_t row_off = (start_vcol < width1 || width2 <= 0)
? 0 ? 0
@ -975,9 +984,9 @@ int64_t win_text_height(win_T *const wp, const linenr_T start_lnum, const int64_
while (lnum <= end_lnum) { while (lnum <= end_lnum) {
linenr_T lnum_next = lnum; linenr_T lnum_next = lnum;
const bool folded = hasFolding(wp, lnum, &lnum, &lnum_next); hasFolding(wp, lnum, &lnum, &lnum_next);
height_sum_fill += win_get_fill(wp, lnum); height_sum_fill += win_get_fill(wp, lnum);
height_cur_nofill = folded ? 1 : plines_win_nofill(wp, lnum, false); height_cur_nofill = plines_win_nofill(wp, lnum, false);
height_sum_nofill += height_cur_nofill; height_sum_nofill += height_cur_nofill;
lnum = lnum_next + 1; lnum = lnum_next + 1;
} }

View File

@ -641,9 +641,6 @@ bool terminal_enter(void)
curwin->w_p_so = 0; curwin->w_p_so = 0;
curwin->w_p_siso = 0; curwin->w_p_siso = 0;
// Save the existing cursor entry since it may be modified by the application
cursorentry_T save_cursorentry = shape_table[SHAPE_IDX_TERM];
// Update the cursor shape table and flush changes to the UI // Update the cursor shape table and flush changes to the UI
s->term->pending.cursor = true; s->term->pending.cursor = true;
refresh_cursor(s->term); refresh_cursor(s->term);
@ -674,8 +671,8 @@ bool terminal_enter(void)
RedrawingDisabled = s->save_rd; RedrawingDisabled = s->save_rd;
apply_autocmds(EVENT_TERMLEAVE, NULL, NULL, false, curbuf); apply_autocmds(EVENT_TERMLEAVE, NULL, NULL, false, curbuf);
shape_table[SHAPE_IDX_TERM] = save_cursorentry; // Restore the terminal cursor to what is set in 'guicursor'
ui_mode_info_set(); (void)parse_shape_opt(SHAPE_CURSOR);
if (save_curwin == curwin->handle) { // Else: window was closed. if (save_curwin == curwin->handle) { // Else: window was closed.
curwin->w_p_cul = save_w_p_cul; curwin->w_p_cul = save_w_p_cul;

View File

@ -6465,9 +6465,9 @@ void win_fix_scroll(bool resize)
// Add difference in height and row to botline. // Add difference in height and row to botline.
if (diff > 0) { if (diff > 0) {
cursor_down_inner(wp, diff); cursor_down_inner(wp, diff, false);
} else { } else {
cursor_up_inner(wp, -diff); cursor_up_inner(wp, -diff, false);
} }
// Scroll to put the new cursor position at the bottom of the // Scroll to put the new cursor position at the bottom of the
@ -6514,11 +6514,11 @@ static void win_fix_cursor(bool normal)
linenr_T lnum = wp->w_cursor.lnum; linenr_T lnum = wp->w_cursor.lnum;
wp->w_cursor.lnum = wp->w_topline; wp->w_cursor.lnum = wp->w_topline;
cursor_down_inner(wp, so); cursor_down_inner(wp, so, false);
linenr_T top = wp->w_cursor.lnum; linenr_T top = wp->w_cursor.lnum;
wp->w_cursor.lnum = wp->w_botline - 1; wp->w_cursor.lnum = wp->w_botline - 1;
cursor_up_inner(wp, so); cursor_up_inner(wp, so, false);
linenr_T bot = wp->w_cursor.lnum; linenr_T bot = wp->w_cursor.lnum;
wp->w_cursor.lnum = lnum; wp->w_cursor.lnum = lnum;
@ -6612,7 +6612,7 @@ void scroll_to_fraction(win_T *wp, int prev_height)
hasFolding(wp, lnum, &lnum, NULL); hasFolding(wp, lnum, &lnum, NULL);
if (lnum == 1) { if (lnum == 1) {
// first line in buffer is folded // first line in buffer is folded
line_size = 1; line_size = !decor_conceal_line(wp, lnum - 1, false);
sline--; sline--;
break; break;
} }

View File

@ -1552,6 +1552,7 @@ describe('API/extmarks', function()
it('can get details', function() it('can get details', function()
set_extmark(ns, marks[1], 0, 0, { set_extmark(ns, marks[1], 0, 0, {
conceal = 'c', conceal = 'c',
conceal_lines = '',
cursorline_hl_group = 'Statement', cursorline_hl_group = 'Statement',
end_col = 0, end_col = 0,
end_right_gravity = true, end_right_gravity = true,
@ -1586,6 +1587,7 @@ describe('API/extmarks', function()
0, 0,
{ {
conceal = 'c', conceal = 'c',
conceal_lines = '',
cursorline_hl_group = 'Statement', cursorline_hl_group = 'Statement',
end_col = 0, end_col = 0,
end_right_gravity = true, end_right_gravity = true,

View File

@ -15,9 +15,20 @@ local skip = t.skip
describe(':terminal cursor', function() describe(':terminal cursor', function()
local screen local screen
local terminal_mode_idx ---@type number
before_each(function() before_each(function()
clear() clear()
screen = tt.setup_screen() screen = tt.setup_screen()
if terminal_mode_idx == nil then
for i, v in ipairs(screen._mode_info) do
if v.name == 'terminal' then
terminal_mode_idx = i
end
end
assert(terminal_mode_idx)
end
end) end)
it('moves the screen cursor when focused', function() it('moves the screen cursor when focused', function()
@ -143,13 +154,6 @@ describe(':terminal cursor', function()
it('can be modified by application #3681', function() it('can be modified by application #3681', function()
skip(is_os('win'), '#31587') skip(is_os('win'), '#31587')
local idx ---@type number
for i, v in ipairs(screen._mode_info) do
if v.name == 'terminal' then
idx = i
end
end
assert(idx)
local states = { local states = {
[1] = { blink = true, shape = 'block' }, [1] = { blink = true, shape = 'block' },
@ -171,13 +175,13 @@ describe(':terminal cursor', function()
]], ]],
condition = function() condition = function()
if v.blink then if v.blink then
eq(500, screen._mode_info[idx].blinkon) eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff) eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
else else
eq(0, screen._mode_info[idx].blinkon) eq(0, screen._mode_info[terminal_mode_idx].blinkon)
eq(0, screen._mode_info[idx].blinkoff) eq(0, screen._mode_info[terminal_mode_idx].blinkoff)
end end
eq(v.shape, screen._mode_info[idx].cursor_shape) eq(v.shape, screen._mode_info[terminal_mode_idx].cursor_shape)
end, end,
}) })
end end
@ -191,20 +195,13 @@ describe(':terminal cursor', function()
]]) ]])
-- Cursor returns to default on TermLeave -- Cursor returns to default on TermLeave
eq(500, screen._mode_info[idx].blinkon) eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff) eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
eq('block', screen._mode_info[idx].cursor_shape) eq('block', screen._mode_info[terminal_mode_idx].cursor_shape)
end) end)
it('can be modified per terminal', function() it('can be modified per terminal', function()
skip(is_os('win'), '#31587') skip(is_os('win'), '#31587')
local idx ---@type number
for i, v in ipairs(screen._mode_info) do
if v.name == 'terminal' then
idx = i
end
end
assert(idx)
-- Set cursor to vertical bar with blink -- Set cursor to vertical bar with blink
tt.feed_csi('5 q') tt.feed_csi('5 q')
@ -216,9 +213,9 @@ describe(':terminal cursor', function()
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]], ]],
condition = function() condition = function()
eq(500, screen._mode_info[idx].blinkon) eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff) eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
eq('vertical', screen._mode_info[idx].cursor_shape) eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape)
end, end,
}) })
@ -231,9 +228,9 @@ describe(':terminal cursor', function()
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]], ]],
condition = function() condition = function()
eq(500, screen._mode_info[idx].blinkon) eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff) eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
eq('vertical', screen._mode_info[idx].cursor_shape) eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape)
end, end,
}) })
@ -256,9 +253,9 @@ describe(':terminal cursor', function()
]], ]],
condition = function() condition = function()
-- New terminal, cursor resets to defaults -- New terminal, cursor resets to defaults
eq(500, screen._mode_info[idx].blinkon) eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff) eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
eq('block', screen._mode_info[idx].cursor_shape) eq('block', screen._mode_info[terminal_mode_idx].cursor_shape)
end, end,
}) })
@ -275,9 +272,9 @@ describe(':terminal cursor', function()
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]], ]],
condition = function() condition = function()
eq(0, screen._mode_info[idx].blinkon) eq(0, screen._mode_info[terminal_mode_idx].blinkon)
eq(0, screen._mode_info[idx].blinkoff) eq(0, screen._mode_info[terminal_mode_idx].blinkoff)
eq('horizontal', screen._mode_info[idx].cursor_shape) eq('horizontal', screen._mode_info[terminal_mode_idx].cursor_shape)
end, end,
}) })
@ -294,9 +291,9 @@ describe(':terminal cursor', function()
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]], ]],
condition = function() condition = function()
eq(500, screen._mode_info[idx].blinkon) eq(500, screen._mode_info[terminal_mode_idx].blinkon)
eq(500, screen._mode_info[idx].blinkoff) eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
eq('vertical', screen._mode_info[idx].cursor_shape) eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape)
end, end,
}) })
end) end)
@ -326,6 +323,32 @@ describe(':terminal cursor', function()
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]]) ]])
end) end)
it('preserves guicursor value on TermLeave #31612', function()
eq(3, screen._mode_info[terminal_mode_idx].hl_id)
-- Change 'guicursor' while terminal mode is active
command('set guicursor+=t:Error')
local error_hl_id = call('hlID', 'Error')
screen:expect({
condition = function()
eq(error_hl_id, screen._mode_info[terminal_mode_idx].hl_id)
end,
})
-- Exit terminal mode
feed([[<C-\><C-N>]])
screen:expect([[
tty ready |
^ |
|*5
]])
eq(error_hl_id, screen._mode_info[terminal_mode_idx].hl_id)
end)
end) end)
describe('buffer cursor position is correct in terminal without number column', function() describe('buffer cursor position is correct in terminal without number column', function()
@ -350,12 +373,6 @@ describe('buffer cursor position is correct in terminal without number column',
}, { }, {
cols = 70, cols = 70,
}) })
screen:set_default_attr_ids({
[1] = { foreground = 253, background = 11 },
[2] = { reverse = true },
[3] = { bold = true },
[4] = { background = 11 },
})
-- Also check for real cursor position, as it is used for stuff like input methods -- Also check for real cursor position, as it is used for stuff like input methods
screen._handle_busy_start = function() end screen._handle_busy_start = function() end
screen._handle_busy_stop = function() end screen._handle_busy_stop = function() end
@ -667,13 +684,6 @@ describe('buffer cursor position is correct in terminal with number column', fun
}, { }, {
cols = 70, cols = 70,
}) })
screen:set_default_attr_ids({
[1] = { foreground = 253, background = 11 },
[2] = { reverse = true },
[3] = { bold = true },
[4] = { background = 11 },
[7] = { foreground = 130 },
})
-- Also check for real cursor position, as it is used for stuff like input methods -- Also check for real cursor position, as it is used for stuff like input methods
screen._handle_busy_start = function() end screen._handle_busy_start = function() end
screen._handle_busy_stop = function() end screen._handle_busy_stop = function() end

View File

@ -1119,14 +1119,16 @@ describe('treesitter highlighting (markdown)', function()
}) })
end) end)
it('works with spellchecked and smoothscrolled topline', function() local code_block = [[
insert([[
- $f(0)=\sum_{k=1}^{\infty}\frac{2}{\pi^{2}k^{2}}+\lim_{w \to 0}x$. - $f(0)=\sum_{k=1}^{\infty}\frac{2}{\pi^{2}k^{2}}+\lim_{w \to 0}x$.
```c ```c
printf('Hello World!'); printf('Hello World!');
``` ```
]]) ]]
it('works with spellchecked and smoothscrolled topline', function()
insert(code_block)
command('set spell smoothscroll') command('set spell smoothscroll')
feed('gg<C-E>') feed('gg<C-E>')
screen:add_extra_attr_ids({ [100] = { undercurl = true, special = Screen.colors.Red } }) screen:add_extra_attr_ids({ [100] = { undercurl = true, special = Screen.colors.Red } })
@ -1141,6 +1143,105 @@ printf('Hello World!');
]], ]],
}) })
end) end)
it('works with concealed lines', function()
insert(code_block)
screen:expect({
grid = [[
|
{18:```}{15:c} |
{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{18:```} |
^ |
|
]],
})
feed('ggj')
command('set number conceallevel=3')
screen:expect({
grid = [[
{8: 1 }{16:- }$f(0)=\sum_{k=1}^{\infty}\frac{2}{|
{8: }\pi^{2}k^{2}}+\lim_{w \to 0}x$. |
{8: 2 }^ |
{8: 4 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{8: 6 } |
|
]],
})
feed('j')
screen:expect({
grid = [[
{8: 1 }{16:- }$f(0)=\sum_{k=1}^{\infty}\frac{2}{|
{8: }\pi^{2}k^{2}}+\lim_{w \to 0}x$. |
{8: 2 } |
{8: 3 }{18:^```}{15:c} |
{8: 4 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
|
]],
})
feed('j')
screen:expect({
grid = [[
{8: 1 }{16:- }$f(0)=\sum_{k=1}^{\infty}\frac{2}{|
{8: }\pi^{2}k^{2}}+\lim_{w \to 0}x$. |
{8: 2 } |
{8: 4 }{25:^printf}{16:(}{26:'Hello World!'}{16:);} |
{8: 6 } |
|
]],
})
feed('j')
screen:expect({
grid = [[
{8: 1 }{16:- }$f(0)=\sum_{k=1}^{\infty}\frac{2}{|
{8: }\pi^{2}k^{2}}+\lim_{w \to 0}x$. |
{8: 2 } |
{8: 4 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{8: 5 }{18:^```} |
|
]],
})
-- Concealed lines highlight until changed botline
screen:try_resize(screen._width, 16)
feed('y3k30P:<Esc><C-F><C-B>')
screen:expect([[
{8: 1 }{16:- }$f(0)=\sum_{k=1}^{\infty}\frac{2}{|
{8: }\pi^{2}k^{2}}+\lim_{w \to 0}x$. |
{8: 2 } |
{8: 4 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{8: 6 } |
{8: 8 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{8: 10 } |
{8: 12 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{8: 14 } |
{8: 16 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{8: 18 } |
{8: 20 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{8: 22 } |
{8: 24 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{8: 25 }{18:^```} |
|
]])
feed('G')
screen:expect([[
{8: 98 } |
{8:100 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{8:102 } |
{8:104 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{8:106 } |
{8:108 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{8:110 } |
{8:112 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{8:114 } |
{8:116 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{8:118 } |
{8:120 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{8:122 } |
{8:124 }{25:printf}{16:(}{26:'Hello World!'}{16:);} |
{8:126 } ^ |
|
]])
end)
end) end)
it('starting and stopping treesitter highlight in init.lua works #29541', function() it('starting and stopping treesitter highlight in init.lua works #29541', function()

View File

@ -2495,6 +2495,209 @@ describe('extmark decorations', function()
| |
]]) ]])
end) end)
it ('conceal_lines', function()
insert(example_text)
exec('set number conceallevel=3')
feed('ggj')
local not_concealed = {
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 2 }^ local text, hl_id_cell, count = unpack(ite|
{2: }m) |
{2: 3 } if hl_id_cell ~= nil then |
{2: 4 } hl_id = hl_id_cell |
{2: 5 } end |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|
|
]]
}
screen:expect(not_concealed)
api.nvim_buf_set_extmark(0, ns, 1, 0, { conceal_lines = "" })
screen:expect_unchanged()
feed('j')
local concealed = {
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 3 }^ if hl_id_cell ~= nil then |
{2: 4 } hl_id = hl_id_cell |
{2: 5 } end |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*3
|
]]
}
screen:expect(concealed)
feed('k')
screen:expect(not_concealed)
exec('set concealcursor=n')
screen:expect(concealed)
api.nvim_buf_set_extmark(0, ns, 3, 0, { conceal_lines = "" })
screen:expect({
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 3 }^ if hl_id_cell ~= nil then |
{2: 5 } end |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*4
|
]]
})
feed('kjj')
screen:expect_unchanged()
api.nvim_buf_set_extmark(0, ns, 4, 0, { conceal_lines = "" })
feed('kjjjC')
screen:expect({
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 3 } if hl_id_cell ~= nil then |
{2: 5 }^ |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*4
{24:-- INSERT --} |
]]
})
feed('<esc>')
screen:expect({
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 3 } if hl_id_cell ~= nil then |
{2: 6 }^ for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*5
|
]]
})
feed('kji')
screen:expect({
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 3 } if hl_id_cell ~= nil then |
{2: 5 }^ |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*4
{24:-- INSERT --} |
]]
})
feed('conceal text')
screen:expect({
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 3 } if hl_id_cell ~= nil then |
{2: 5 }conceal text^ |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*4
{24:-- INSERT --} |
]]
})
feed('<esc>')
screen:expect({
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 3 } if hl_id_cell ~= nil then |
{2: 6 } for _ =^ 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*5
|
]]
})
feed('ggzfj')
screen:expect({
grid = [[
{2: 1 }{33:^+-- 2 lines: for _,item in ipairs(items) do··}|
{2: 3 } if hl_id_cell ~= nil then |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*5
|
]]
})
feed('j')
screen:expect({
grid = [[
{2: 1 }{33:+-- 2 lines: for _,item in ipairs(items) do··}|
{2: 3 }^ if hl_id_cell ~= nil then |
{2: 6 } for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*5
|
]]
})
feed('ggzdjzfj')
screen:expect({
grid = [[
{2: 1 }for _,item in ipairs(items) do |
{2: 6 }^ for _ = 1, (count or 1) do |
{2: 7 } local cell = line[colpos] |
{2: 8 } cell.text = text |
{2: 9 } cell.hl_id = hl_id |
{2: 10 } colpos = colpos+1 |
{2: 11 } end |
{2: 12 }end |
{1:~ }|*6
|
]]
})
feed('jj')
screen:expect_unchanged()
end)
end) end)
describe('decorations: inline virtual text', function() describe('decorations: inline virtual text', function()

View File

@ -94,7 +94,7 @@ describe('ui mode_change event', function()
} }
end) end)
-- oldtest: Test_indent_norm_with_gq() -- oldtest: Test_mouse_shape_indent_norm_with_gq()
it('is restored to Normal mode after "gq" indents using :normal #12309', function() it('is restored to Normal mode after "gq" indents using :normal #12309', function()
screen:try_resize(60, 6) screen:try_resize(60, 6)
n.exec([[ n.exec([[

View File

@ -1162,6 +1162,8 @@ describe('builtin popupmenu', function()
[6] = { foreground = Screen.colors.White, background = Screen.colors.Red }, [6] = { foreground = Screen.colors.White, background = Screen.colors.Red },
[7] = { background = Screen.colors.Yellow }, -- Search [7] = { background = Screen.colors.Yellow }, -- Search
[8] = { foreground = Screen.colors.Red }, [8] = { foreground = Screen.colors.Red },
[9] = { foreground = Screen.colors.Yellow, background = Screen.colors.Green },
[10] = { foreground = Screen.colors.White, background = Screen.colors.Green },
ks = { foreground = Screen.colors.Red, background = Screen.colors.Grey }, ks = { foreground = Screen.colors.Red, background = Screen.colors.Grey },
kn = { foreground = Screen.colors.Red, background = Screen.colors.Plum1 }, kn = { foreground = Screen.colors.Red, background = Screen.colors.Plum1 },
xs = { foreground = Screen.colors.Black, background = Screen.colors.Grey }, xs = { foreground = Screen.colors.Black, background = Screen.colors.Grey },
@ -5625,6 +5627,114 @@ describe('builtin popupmenu', function()
{2:-- INSERT --} | {2:-- INSERT --} |
]]) ]])
feed('<Esc>') feed('<Esc>')
-- text after the inserted text shouldn't be highlighted
feed('0ea <C-X><C-O>')
screen:expect([[
αβγ {8:foo}^ foo |
{1:~ }{s: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{n: }{1: }|
{1:~ }|*15
{2:-- }{5:match 1 of 3} |
]])
feed('<C-P>')
screen:expect([[
αβγ ^ foo |
{1:~ }{n: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{n: }{1: }|
{1:~ }|*15
{2:-- }{8:Back at original} |
]])
feed('<C-P>')
screen:expect([[
αβγ {8:}^ foo |
{1:~ }{n: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{s: }{1: }|
{1:~ }|*15
{2:-- }{5:match 3 of 3} |
]])
feed('<C-Y>')
screen:expect([[
αβγ ^ foo |
{1:~ }|*18
{2:-- INSERT --} |
]])
feed('<Esc>')
end)
-- oldtest: Test_pum_matchins_highlight_combine()
it('with ComplMatchIns, Normal and CursorLine highlights', function()
exec([[
func Omni_test(findstart, base)
if a:findstart
return col(".")
endif
return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}]
endfunc
set omnifunc=Omni_test
hi Normal guibg=blue
hi CursorLine guibg=green guifg=white
set cursorline
call setline(1, 'aaa bbb')
]])
-- when ComplMatchIns is not set, CursorLine applies normally
feed('0ea <C-X><C-O>')
screen:expect([[
{10:aaa foo^ bbb }|
{1:~ }{s: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{n: }{1: }|
{1:~ }|*15
{2:-- }{5:match 1 of 3} |
]])
feed('<C-E>')
screen:expect([[
{10:aaa ^ bbb }|
{1:~ }|*18
{2:-- INSERT --} |
]])
feed('<BS><Esc>')
-- when ComplMatchIns is set, it is applied over CursorLine
command('hi ComplMatchIns guifg=Yellow')
feed('0ea <C-X><C-O>')
screen:expect([[
{10:aaa }{9:foo}{10:^ bbb }|
{1:~ }{s: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{n: }{1: }|
{1:~ }|*15
{2:-- }{5:match 1 of 3} |
]])
feed('<C-P>')
screen:expect([[
{10:aaa ^ bbb }|
{1:~ }{n: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{n: }{1: }|
{1:~ }|*15
{2:-- }{8:Back at original} |
]])
feed('<C-P>')
screen:expect([[
{10:aaa }{9:}{10:^ bbb }|
{1:~ }{n: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{s: }{1: }|
{1:~ }|*15
{2:-- }{5:match 3 of 3} |
]])
feed('<C-E>')
screen:expect([[
{10:aaa ^ bbb }|
{1:~ }|*18
{2:-- INSERT --} |
]])
feed('<Esc>')
end) end)
end end
end end

View File

@ -308,7 +308,7 @@ endfunc
" Test that mouse shape is restored to Normal mode after using "gq" when " Test that mouse shape is restored to Normal mode after using "gq" when
" 'indentexpr' executes :normal. " 'indentexpr' executes :normal.
func Test_indent_norm_with_gq() func Test_mouse_shape_indent_norm_with_gq()
CheckFeature mouseshape CheckFeature mouseshape
CheckCanRunGui CheckCanRunGui

View File

@ -1747,13 +1747,67 @@ func Test_pum_matchins_highlight()
call TermWait(buf) call TermWait(buf)
call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<C-Y>") call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<C-Y>")
call VerifyScreenDump(buf, 'Test_pum_matchins_04', {}) call VerifyScreenDump(buf, 'Test_pum_matchins_04', {})
call term_sendkeys(buf, "\<C-E>\<Esc>") call term_sendkeys(buf, "\<Esc>")
" restore after cancel completion " restore after cancel completion
call TermWait(buf) call TermWait(buf)
call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<Space>") call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<Space>")
call VerifyScreenDump(buf, 'Test_pum_matchins_05', {}) call VerifyScreenDump(buf, 'Test_pum_matchins_05', {})
call term_sendkeys(buf, "\<C-E>\<Esc>") call term_sendkeys(buf, "\<Esc>")
" text after the inserted text shouldn't be highlighted
call TermWait(buf)
call term_sendkeys(buf, "0ea \<C-X>\<C-O>")
call VerifyScreenDump(buf, 'Test_pum_matchins_07', {})
call term_sendkeys(buf, "\<C-P>")
call VerifyScreenDump(buf, 'Test_pum_matchins_08', {})
call term_sendkeys(buf, "\<C-P>")
call VerifyScreenDump(buf, 'Test_pum_matchins_09', {})
call term_sendkeys(buf, "\<C-Y>")
call VerifyScreenDump(buf, 'Test_pum_matchins_10', {})
call term_sendkeys(buf, "\<Esc>")
call StopVimInTerminal(buf)
endfunc
func Test_pum_matchins_highlight_combine()
CheckScreendump
let lines =<< trim END
func Omni_test(findstart, base)
if a:findstart
return col(".")
endif
return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}]
endfunc
set omnifunc=Omni_test
hi Normal ctermbg=blue
hi CursorLine cterm=underline ctermbg=green
set cursorline
call setline(1, 'aaa bbb')
END
call writefile(lines, 'Xscript', 'D')
let buf = RunVimInTerminal('-S Xscript', {})
" when ComplMatchIns is not set, CursorLine applies normally
call term_sendkeys(buf, "0ea \<C-X>\<C-O>")
call VerifyScreenDump(buf, 'Test_pum_matchins_combine_01', {})
call term_sendkeys(buf, "\<C-E>")
call VerifyScreenDump(buf, 'Test_pum_matchins_combine_02', {})
call term_sendkeys(buf, "\<BS>\<Esc>")
" when ComplMatchIns is set, it is applied over CursorLine
call TermWait(buf)
call term_sendkeys(buf, ":hi ComplMatchIns ctermbg=red ctermfg=yellow\<CR>")
call TermWait(buf)
call term_sendkeys(buf, "0ea \<C-X>\<C-O>")
call VerifyScreenDump(buf, 'Test_pum_matchins_combine_03', {})
call term_sendkeys(buf, "\<C-P>")
call VerifyScreenDump(buf, 'Test_pum_matchins_combine_04', {})
call term_sendkeys(buf, "\<C-P>")
call VerifyScreenDump(buf, 'Test_pum_matchins_combine_05', {})
call term_sendkeys(buf, "\<C-E>")
call VerifyScreenDump(buf, 'Test_pum_matchins_combine_06', {})
call term_sendkeys(buf, "\<Esc>")
call StopVimInTerminal(buf) call StopVimInTerminal(buf)
endfunc endfunc