diff options
35 files changed, 820 insertions, 244 deletions
diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index 945f8d5f45..4c23803560 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -4,8 +4,8 @@ LIBUV_SHA256 7aa66be3413ae10605e1f5c9ae934504ffe317ef68ea16fdaa83e23905c681bd MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/c-6.0.0/msgpack-c-6.0.0.tar.gz MSGPACK_SHA256 3654f5e2c652dc52e0a993e270bb57d5702b262703f03771c152bba51602aeba -LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/e897c5743f97a6b05c59852709092e7da4119914.tar.gz -LUAJIT_SHA256 f28030c61602bffd28a445f3fef23198264a845aab94d909587bac6c12c8b874 +LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/d1a2fef8a8f53b0055ee041f7f63d83a27444ffa.tar.gz +LUAJIT_SHA256 7d4dd85cbf224c70f2014203903202a90f3cfe7b1c6e7b1b36f8ae472dfc7fba LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333 diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index 0fa5ffd598..ce2ec36ca3 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -1093,11 +1093,11 @@ To get basic "autocompletion" without installing a plugin, try this script: >lua vim.api.nvim_create_autocmd("InsertCharPre", { buffer = vim.api.nvim_get_current_buf(), callback = function() - if vim.fn.pumvisible() == 1 then + if vim.fn.pumvisible() == 1 or vim.fn.state("m") == "m" then return end local char = vim.v.char - if vim.tbl_contains(triggers, char) then + if vim.list_contains(triggers, char) then local key = vim.keycode("<C-x><C-n>") vim.api.nvim_feedkeys(key, "m", false) end diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 15292cd7cf..23902f8bc3 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -1430,7 +1430,7 @@ display({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.display()* Display the lenses using virtual text Parameters: ~ - • {lenses} (table) of lenses to display (`CodeLens[] | null`) + • {lenses} lsp.CodeLens[]|nil lenses to display • {bufnr} (integer) • {client_id} (integer) @@ -1442,7 +1442,7 @@ get({bufnr}) *vim.lsp.codelens.get()* buffer. Return: ~ - (table) (`CodeLens[]`) + lsp.CodeLens[] *vim.lsp.codelens.on_codelens()* on_codelens({err}, {result}, {ctx}, {_}) @@ -1464,7 +1464,7 @@ save({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.save()* Store lenses for a specific buffer and client Parameters: ~ - • {lenses} (table) of lenses to store (`CodeLens[] | null`) + • {lenses} lsp.CodeLens[]|nil lenses to store • {bufnr} (integer) • {client_id} (integer) diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt index c4e144ab29..5e18595d22 100644 --- a/runtime/doc/motion.txt +++ b/runtime/doc/motion.txt @@ -1050,14 +1050,14 @@ can go to cursor positions before older jumps, and back again. Thus you can move up and down the list. There is a separate jump list for each window. The maximum number of entries is fixed at 100. -For example, after three jump commands you have this jump list: - - jump line col file/text ~ - 3 1 0 some text ~ - 2 70 0 another line ~ - 1 1154 23 end. ~ - > ~ +For example, after three jump commands you have this jump list: > + jump line col file/text + 3 1 0 some text + 2 70 0 another line + 1 1154 23 end. + > +< The "file/text" column shows the file name, or the text at the jump if it is in the current file (an indent is removed and a long line is truncated to fit in the window). @@ -1066,14 +1066,14 @@ The marker ">" indicates the current position in the jumplist. It may not be shown when filtering the |:jumps| command using |:filter| You are currently in line 1167. If you then use the CTRL-O command, the -cursor is put in line 1154. This results in: - - jump line col file/text ~ - 2 1 0 some text ~ - 1 70 0 another line ~ - > 0 1154 23 end. ~ - 1 1167 0 foo bar ~ +cursor is put in line 1154. This results in: > + jump line col file/text + 2 1 0 some text + 1 70 0 another line + > 0 1154 23 end. + 1 1167 0 foo bar +< The pointer will be set at the last used jump position. The next CTRL-O command will use the entry above it, the next CTRL-I command will use the entry below it. If the pointer is below the last entry, this indicates that @@ -1097,15 +1097,15 @@ command. You can explicitly add a jump by setting the ' mark with "m'". Note that calling setpos() does not do this. After the CTRL-O command that got you into line 1154 you could give another -jump command (e.g., "G"). The jump list would then become: - - jump line col file/text ~ - 4 1 0 some text ~ - 3 70 0 another line ~ - 2 1167 0 foo bar ~ - 1 1154 23 end. ~ - > ~ - +jump command (e.g., "G"). The jump list would then become: > + + jump line col file/text + 4 1 0 some text + 3 70 0 another line + 2 1167 0 foo bar + 1 1154 23 end. + > +< The line numbers will be adjusted for deleted and inserted lines. This fails if you stop editing a file without writing, like with ":n!". @@ -1141,7 +1141,6 @@ locations being removed: > 1 462 36 eval.c <-- location X > < - Then, when yet another location Z is jumped to, the new location Y appears directly after location X in the jumplist and location X remains in the same position relative to the locations (X-1, X-2, etc., ...) that had been before @@ -1151,7 +1150,7 @@ it prior to the original jump from X to Y: > 4 1260 8 mark.c <-- location X-2 3 685 0 eval.c <-- location X-1 2 462 36 eval.c <-- location X - 1 100 0 buffer.c <-- location Y + 1 100 0 buffer.c <-- location Y > < CHANGE LIST JUMPS *changelist* *change-list-jumps* *E664* diff --git a/runtime/lua/vim/_meta/json.lua b/runtime/lua/vim/_meta/json.lua index edf905c7c6..e010086615 100644 --- a/runtime/lua/vim/_meta/json.lua +++ b/runtime/lua/vim/_meta/json.lua @@ -1,5 +1,8 @@ ---@meta +---@nodoc +vim.json = {} + -- luacheck: no unused args ---@defgroup vim.json diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index 384d09ee48..9cccaa1d66 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -8,6 +8,7 @@ local M = {} --- to throttle refreshes to at most one at a time local active_refreshes = {} +---@type table<integer, table<integer, lsp.CodeLens[]>> --- bufnr -> client_id -> lenses local lens_cache_by_buf = setmetatable({}, { __index = function(t, b) @@ -16,6 +17,8 @@ local lens_cache_by_buf = setmetatable({}, { end, }) +---@type table<integer, integer> +---client_id -> namespace local namespaces = setmetatable({}, { __index = function(t, key) local value = api.nvim_create_namespace('vim_lsp_codelens:' .. key) @@ -27,6 +30,18 @@ local namespaces = setmetatable({}, { ---@private M.__namespaces = namespaces +local augroup = api.nvim_create_augroup('vim_lsp_codelens', {}) + +api.nvim_create_autocmd('LspDetach', { + group = augroup, + callback = function(ev) + M.clear(ev.data.client_id, ev.buf) + end, +}) + +---@param lens lsp.CodeLens +---@param bufnr integer +---@param client_id integer local function execute_lens(lens, bufnr, client_id) local line = lens.range.start.line api.nvim_buf_clear_namespace(bufnr, namespaces[client_id], line, line + 1) @@ -42,7 +57,7 @@ end --- Return all lenses for the given buffer --- ---@param bufnr integer Buffer number. 0 can be used for the current buffer. ----@return table (`CodeLens[]`) +---@return lsp.CodeLens[] function M.get(bufnr) local lenses_by_client = lens_cache_by_buf[bufnr or 0] if not lenses_by_client then @@ -98,12 +113,17 @@ end ---@param client_id integer|nil filter by client_id. All clients if nil ---@param bufnr integer|nil filter by buffer. All buffers if nil function M.clear(client_id, bufnr) - local buffers = bufnr and { resolve_bufnr(bufnr) } or vim.tbl_keys(lens_cache_by_buf) + bufnr = bufnr and resolve_bufnr(bufnr) + local buffers = bufnr and { bufnr } + or vim.tbl_filter(api.nvim_buf_is_loaded, api.nvim_list_bufs()) for _, iter_bufnr in pairs(buffers) do local client_ids = client_id and { client_id } or vim.tbl_keys(namespaces) for _, iter_client_id in pairs(client_ids) do local ns = namespaces[iter_client_id] - lens_cache_by_buf[iter_bufnr][iter_client_id] = {} + -- there can be display()ed lenses, which are not stored in cache + if lens_cache_by_buf[iter_bufnr] then + lens_cache_by_buf[iter_bufnr][iter_client_id] = {} + end api.nvim_buf_clear_namespace(iter_bufnr, ns, 0, -1) end end @@ -111,7 +131,7 @@ end --- Display the lenses using virtual text --- ----@param lenses table of lenses to display (`CodeLens[] | null`) +---@param lenses? lsp.CodeLens[] lenses to display ---@param bufnr integer ---@param client_id integer function M.display(lenses, bufnr, client_id) @@ -124,7 +144,8 @@ function M.display(lenses, bufnr, client_id) api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) return end - local lenses_by_lnum = {} + + local lenses_by_lnum = {} ---@type table<integer, lsp.CodeLens[]> for _, lens in pairs(lenses) do local line_lenses = lenses_by_lnum[lens.range.start.line] if not line_lenses then @@ -160,7 +181,7 @@ end --- Store lenses for a specific buffer and client --- ----@param lenses table of lenses to store (`CodeLens[] | null`) +---@param lenses? lsp.CodeLens[] lenses to store ---@param bufnr integer ---@param client_id integer function M.save(lenses, bufnr, client_id) @@ -174,7 +195,7 @@ function M.save(lenses, bufnr, client_id) lens_cache_by_buf[bufnr] = lenses_by_client local ns = namespaces[client_id] api.nvim_buf_attach(bufnr, false, { - on_detach = function(b) + on_detach = function(_, b) lens_cache_by_buf[b] = nil end, on_lines = function(_, b, _, first_lnum, last_lnum) @@ -185,6 +206,10 @@ function M.save(lenses, bufnr, client_id) lenses_by_client[client_id] = lenses end +---@param lenses? lsp.CodeLens[] +---@param bufnr integer +---@param client_id integer +---@param callback fun() local function resolve_lenses(lenses, bufnr, client_id, callback) lenses = lenses or {} local num_lens = vim.tbl_count(lenses) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 988057f5f9..0d1e3cc0d1 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -2230,6 +2230,35 @@ function M.lookup_section(settings, section) return settings end +--- Converts line range (0-based, end-inclusive) to lsp range, +--- handles absence of a trailing newline +--- +---@param bufnr integer +---@param start_line integer +---@param end_line integer +---@param offset_encoding lsp.PositionEncodingKind +---@return lsp.Range +local function make_line_range_params(bufnr, start_line, end_line, offset_encoding) + local last_line = api.nvim_buf_line_count(bufnr) - 1 + + ---@type lsp.Position + local end_pos + + if end_line == last_line and not vim.api.nvim_get_option_value('endofline', { buf = bufnr }) then + end_pos = { + line = end_line, + character = M.character_offset(bufnr, end_line, #get_line(bufnr, end_line), offset_encoding), + } + else + end_pos = { line = end_line + 1, character = 0 } + end + + return { + start = { line = start_line, character = 0 }, + ['end'] = end_pos, + } +end + ---@private --- Request updated LSP information for a buffer. --- @@ -2253,6 +2282,8 @@ function M._refresh(method, opts) return end + local textDocument = M.make_text_document_params(bufnr) + local only_visible = opts.only_visible or false if only_visible then @@ -2260,28 +2291,25 @@ function M._refresh(method, opts) if api.nvim_win_get_buf(window) == bufnr then local first = vim.fn.line('w0', window) local last = vim.fn.line('w$', window) - local params = { - textDocument = M.make_text_document_params(bufnr), - range = { - start = { line = first - 1, character = 0 }, - ['end'] = { line = last, character = 0 }, - }, - } for _, client in ipairs(clients) do - client.request(method, params, nil, bufnr) + client.request(method, { + textDocument = textDocument, + range = make_line_range_params(bufnr, first - 1, last - 1, client.offset_encoding), + }, nil, bufnr) end end end else - local params = { - textDocument = M.make_text_document_params(bufnr), - range = { - start = { line = 0, character = 0 }, - ['end'] = { line = api.nvim_buf_line_count(bufnr), character = 0 }, - }, - } for _, client in ipairs(clients) do - client.request(method, params, nil, bufnr) + client.request(method, { + textDocument = textDocument, + range = make_line_range_params( + bufnr, + 0, + api.nvim_buf_line_count(bufnr) - 1, + client.offset_encoding + ), + }, nil, bufnr) end end end diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index b2c4e9167d..670f2797b7 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -650,8 +650,8 @@ function LanguageTree:included_regions() return self._regions end - if not self._has_regions or next(self._trees) == nil then - -- treesitter.c will default empty ranges to { -1, -1, -1, -1, -1, -1} + if not self._has_regions then + -- treesitter.c will default empty ranges to { -1, -1, -1, -1, -1, -1} (the full range) return { {} } end diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 94e5e6ee28..db79de63cc 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -230,6 +230,11 @@ if(WIN32) endif() elseif(APPLE) target_link_libraries(nvim PRIVATE "-framework CoreServices") + + # Actually export symbols - symbols may not be visible even though + # ENABLE_EXPORTS is set to true. See + # https://github.com/neovim/neovim/issues/25295 + set_target_properties(nvim PROPERTIES LINK_FLAGS "-Wl,-export_dynamic") endif() if(UNIX) diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index faab6e593c..91e197bea7 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -113,6 +113,36 @@ static Object hl_group_name(int hl_id, bool hl_name) } } +Array virt_text_to_array(VirtText vt, bool hl_name) +{ + Array chunks = ARRAY_DICT_INIT; + Array hl_array = ARRAY_DICT_INIT; + for (size_t i = 0; i < kv_size(vt); i++) { + char *text = kv_A(vt, i).text; + int hl_id = kv_A(vt, i).hl_id; + if (text == NULL) { + if (hl_id > 0) { + ADD(hl_array, hl_group_name(hl_id, hl_name)); + } + continue; + } + Array chunk = ARRAY_DICT_INIT; + ADD(chunk, CSTR_TO_OBJ(text)); + if (hl_array.size > 0) { + if (hl_id > 0) { + ADD(hl_array, hl_group_name(hl_id, hl_name)); + } + ADD(chunk, ARRAY_OBJ(hl_array)); + hl_array = (Array)ARRAY_DICT_INIT; + } else if (hl_id > 0) { + ADD(chunk, hl_group_name(hl_id, hl_name)); + } + ADD(chunks, ARRAY_OBJ(chunk)); + } + assert(hl_array.size == 0); + return chunks; +} + static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict, bool hl_name) { Array rv = ARRAY_DICT_INIT; @@ -145,16 +175,7 @@ static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict } if (kv_size(decor->virt_text)) { - Array chunks = ARRAY_DICT_INIT; - for (size_t i = 0; i < decor->virt_text.size; i++) { - Array chunk = ARRAY_DICT_INIT; - VirtTextChunk *vtc = &decor->virt_text.items[i]; - ADD(chunk, CSTR_TO_OBJ(vtc->text)); - if (vtc->hl_id > 0) { - ADD(chunk, hl_group_name(vtc->hl_id, hl_name)); - } - ADD(chunks, ARRAY_OBJ(chunk)); - } + Array chunks = virt_text_to_array(decor->virt_text, hl_name); PUT(dict, "virt_text", ARRAY_OBJ(chunks)); PUT(dict, "virt_text_hide", BOOLEAN_OBJ(decor->virt_text_hide)); if (decor->virt_text_pos == kVTWinCol) { @@ -171,19 +192,9 @@ static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict if (kv_size(decor->virt_lines)) { Array all_chunks = ARRAY_DICT_INIT; bool virt_lines_leftcol = false; - for (size_t i = 0; i < decor->virt_lines.size; i++) { - Array chunks = ARRAY_DICT_INIT; - VirtText *vt = &decor->virt_lines.items[i].line; - virt_lines_leftcol = decor->virt_lines.items[i].left_col; - for (size_t j = 0; j < vt->size; j++) { - Array chunk = ARRAY_DICT_INIT; - VirtTextChunk *vtc = &vt->items[j]; - ADD(chunk, CSTR_TO_OBJ(vtc->text)); - if (vtc->hl_id > 0) { - ADD(chunk, hl_group_name(vtc->hl_id, hl_name)); - } - ADD(chunks, ARRAY_OBJ(chunk)); - } + for (size_t i = 0; i < kv_size(decor->virt_lines); i++) { + virt_lines_leftcol = kv_A(decor->virt_lines, i).left_col; + Array chunks = virt_text_to_array(kv_A(decor->virt_lines, i).line, hl_name); ADD(all_chunks, ARRAY_OBJ(chunks)); } PUT(dict, "virt_lines", ARRAY_OBJ(all_chunks)); @@ -1188,8 +1199,7 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width) goto free_exit; } if (j < arr.size - 1) { - kv_push(virt_text, ((VirtTextChunk){ .text = NULL, - .hl_id = hl_id })); + kv_push(virt_text, ((VirtTextChunk){ .text = NULL, .hl_id = hl_id })); } } } else { diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index fc70215352..6ca5024a04 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -167,4 +167,7 @@ void msg_history_show(Array entries) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void msg_history_clear(void) FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY; + +void error_exit(Integer status) + FUNC_API_SINCE(12); #endif // NVIM_API_UI_EVENTS_IN_H diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index ebdbd896a5..ea0b7ce512 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -259,34 +259,28 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err) Dictionary config_put_bordertext(Dictionary config, FloatConfig *fconfig, BorderTextType bordertext_type) { - VirtText chunks; + VirtText vt; AlignTextPos align; char *field_name; char *field_pos_name; switch (bordertext_type) { case kBorderTextTitle: - chunks = fconfig->title_chunks; + vt = fconfig->title_chunks; align = fconfig->title_pos; field_name = "title"; field_pos_name = "title_pos"; break; case kBorderTextFooter: - chunks = fconfig->footer_chunks; + vt = fconfig->footer_chunks; align = fconfig->footer_pos; field_name = "footer"; field_pos_name = "footer_pos"; break; + default: + abort(); } - Array bordertext = ARRAY_DICT_INIT; - for (size_t i = 0; i < chunks.size; i++) { - Array tuple = ARRAY_DICT_INIT; - ADD(tuple, CSTR_TO_OBJ(chunks.items[i].text)); - if (chunks.items[i].hl_id > 0) { - ADD(tuple, CSTR_TO_OBJ(syn_id2name(chunks.items[i].hl_id))); - } - ADD(bordertext, ARRAY_OBJ(tuple)); - } + Array bordertext = virt_text_to_array(vt, true); PUT(config, field_name, ARRAY_OBJ(bordertext)); char *pos; diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index b70f070a51..9d391cde8c 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -153,6 +153,24 @@ void clear_virttext(VirtText *text) *text = (VirtText)KV_INITIAL_VALUE; } +/// Get the next chunk of a virtual text item. +/// +/// @param[in] vt The virtual text item +/// @param[in,out] pos Position in the virtual text item +/// @param[in,out] attr Highlight attribute +/// +/// @return The text of the chunk, or NULL if there are no more chunks +char *next_virt_text_chunk(VirtText vt, size_t *pos, int *attr) +{ + char *text = NULL; + for (; text == NULL && *pos < kv_size(vt); (*pos)++) { + text = kv_A(vt, *pos).text; + int hl_id = kv_A(vt, *pos).hl_id; + *attr = hl_combine_attr(*attr, hl_id > 0 ? syn_id2attr(hl_id) : 0); + } + return text; +} + Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) { MarkTreeIter itr[1] = { 0 }; diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 73021d83a8..7d64d9fa3c 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -332,23 +332,17 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, size_t virt_pos = 0; while (rl ? col > max_col : col < max_col) { - if (!*s.p) { + if (*s.p == NUL) { if (virt_pos >= kv_size(vt)) { break; } virt_attr = 0; - do { - s.p = kv_A(vt, virt_pos).text; - int hl_id = kv_A(vt, virt_pos).hl_id; - virt_attr = hl_combine_attr(virt_attr, - hl_id > 0 ? syn_id2attr(hl_id) : 0); - virt_pos++; - } while (!s.p && virt_pos < kv_size(vt)); - if (!s.p) { + s.p = next_virt_text_chunk(vt, &virt_pos, &virt_attr); + if (s.p == NULL) { break; } } - if (!*s.p) { + if (*s.p == NUL) { continue; } int attr; @@ -950,17 +944,20 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t } } else { // already inside existing inline virtual text with multiple chunks - VirtTextChunk vtc = kv_A(wlv->virt_inline, wlv->virt_inline_i); - wlv->virt_inline_i++; - wlv->p_extra = vtc.text; - wlv->n_extra = (int)strlen(vtc.text); + int attr = 0; + char *text = next_virt_text_chunk(wlv->virt_inline, &wlv->virt_inline_i, &attr); + if (text == NULL) { + continue; + } + wlv->p_extra = text; + wlv->n_extra = (int)strlen(text); if (wlv->n_extra == 0) { continue; } wlv->c_extra = NUL; wlv->c_final = NUL; - wlv->extra_attr = vtc.hl_id ? syn_id2attr(vtc.hl_id) : 0; - wlv->n_attr = mb_charlen(vtc.text); + wlv->extra_attr = attr; + wlv->n_attr = mb_charlen(text); // If the text didn't reach until the first window // column we need to skip cells. if (wlv->skip_cells > 0) { @@ -3024,14 +3021,17 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl linebuf_attr[wlv.off] = wlv.char_attr; } - linebuf_vcol[wlv.off] = wlv.vcol; - - if (wlv.draw_state == WL_FOLD) { - linebuf_vcol[wlv.off] = -2; + if (wlv.draw_state > WL_STC && wlv.filler_todo <= 0) { + linebuf_vcol[wlv.off] = wlv.vcol; + } else if (wlv.draw_state == WL_FOLD) { if (wlv.n_closing > 0) { linebuf_vcol[wlv.off] = -3; wlv.n_closing--; + } else { + linebuf_vcol[wlv.off] = -2; } + } else { + linebuf_vcol[wlv.off] = -1; } if (utf_char2cells(mb_c) > 1) { @@ -3041,17 +3041,19 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl // UTF-8: Put a 0 in the second screen char. linebuf_char[wlv.off] = 0; linebuf_attr[wlv.off] = linebuf_attr[wlv.off - 1]; + if (wlv.draw_state > WL_STC && wlv.filler_todo <= 0) { - wlv.vcol++; + linebuf_vcol[wlv.off] = ++wlv.vcol; + } else { + linebuf_vcol[wlv.off] = -1; } + // When "wlv.tocol" is halfway through a character, set it to the end // of the character, otherwise highlighting won't stop. if (wlv.tocol == wlv.vcol) { wlv.tocol++; } - linebuf_vcol[wlv.off] = wlv.vcol; - if (wp->w_p_rl) { // now it's time to backup one cell wlv.off--; diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 854c8590e4..1d22b67d40 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -709,13 +709,15 @@ void end_search_hl(void) screen_search_hl.rm.regprog = NULL; } -static void win_redr_bordertext(win_T *wp, ScreenGrid *grid, VirtText text_chunks, int row, int col) +static void win_redr_bordertext(win_T *wp, ScreenGrid *grid, VirtText vt, int row, int col) { - for (size_t i = 0; i < text_chunks.size; i++) { - char *text = text_chunks.items[i].text; + for (size_t i = 0; i < kv_size(vt);) { + int attr = 0; + char *text = next_virt_text_chunk(vt, &i, &attr); + if (text == NULL) { + break; + } int cell = (int)mb_string2cells(text); - int hl_id = text_chunks.items[i].hl_id; - int attr = hl_id ? syn_id2attr(hl_id) : 0; grid_puts(grid, text, row, col, attr); col += cell; } diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 95bf4d1c3b..00ba1334b0 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -423,6 +423,7 @@ static void exit_event(void **argv) if (!exiting) { if (ui_client_channel_id) { + ui_client_exit_status = status; os_exit(status); } else { assert(status == 0); // Called from rpc_close(), which passes 0 as status. diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 44610c81d8..0a0f7c244d 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4591,7 +4591,9 @@ static void ex_cquit(exarg_T *eap) FUNC_ATTR_NORETURN { // this does not always pass on the exit code to the Manx compiler. why? - getout(eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE); + int status = eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE; + ui_call_error_exit(status); + getout(status); } /// Do preparations for "qall" and "wqall". diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 1d5ba49301..d874e904d0 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -3344,8 +3344,13 @@ void f_foldtextresult(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (kv_size(vt) > 0) { assert(*text == NUL); - for (size_t i = 0; i < kv_size(vt); i++) { - char *new_text = concat_str(text, kv_A(vt, i).text); + for (size_t i = 0; i < kv_size(vt);) { + int attr = 0; + char *new_text = next_virt_text_chunk(vt, &i, &attr); + if (new_text == NULL) { + break; + } + new_text = concat_str(text, new_text); xfree(text); text = new_text; } diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index 207bf72816..100d648263 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -44,7 +44,7 @@ enum { /// attrs[] contains the highlighting attribute for each cell. /// /// vcols[] contains the virtual columns in the line. -1 means not available -/// (below last line), MAXCOL means after the end of the line. +/// or before buffer text, MAXCOL means after the end of the line. /// -2 or -3 means in fold column and a mouse click should: /// -2: open a fold /// -3: close a fold diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index e35385dd43..9b09a3fdf3 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -1849,57 +1849,61 @@ static void mouse_check_grid(colnr_T *vcolp, int *flagsp) int click_grid = mouse_grid; int click_row = mouse_row; int click_col = mouse_col; - int max_row = Rows; - int max_col = Columns; - bool multigrid = ui_has(kUIMultigrid); - colnr_T col_from_screen = -1; + // XXX: this doesn't change click_grid if it is 1, even with multigrid win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col); - if (wp && multigrid) { - max_row = wp->w_grid_alloc.rows; - max_col = wp->w_grid_alloc.cols; - } - - if (wp && mouse_row >= 0 && mouse_row < max_row - && mouse_col >= 0 && mouse_col < max_col) { - ScreenGrid *gp = multigrid ? &wp->w_grid_alloc : &default_grid; - int use_row = multigrid && mouse_grid == 0 ? click_row : mouse_row; - int use_col = multigrid && mouse_grid == 0 ? click_col : mouse_col; - - if (gp->chars != NULL) { - const size_t off = gp->line_offset[use_row] + (size_t)use_col; - - // Only use vcols[] after the window was redrawn. Mainly matters - // for tests, a user would not click before redrawing. - if (wp->w_redr_type == 0) { - col_from_screen = gp->vcols[off]; - } - - if (col_from_screen == MAXCOL) { - // When clicking after end of line, still need to set correct curswant - size_t off_l = gp->line_offset[use_row]; - if (gp->vcols[off_l] < MAXCOL) { - // Binary search to find last char in line - size_t off_r = off; - while (off_l < off_r) { - size_t off_m = (off_l + off_r + 1) / 2; - if (gp->vcols[off_m] < MAXCOL) { - off_l = off_m; - } else { - off_r = off_m - 1; - } - } - *vcolp = gp->vcols[off_r] + (int)(off - off_r); + // Only use vcols[] after the window was redrawn. Mainly matters + // for tests, a user would not click before redrawing. + if (wp == NULL || wp->w_redr_type != 0) { + return; + } + ScreenGrid *gp = &wp->w_grid; + int start_row = 0; + int start_col = 0; + grid_adjust(&gp, &start_row, &start_col); + if (gp->handle != click_grid || gp->chars == NULL) { + return; + } + click_row += start_row; + click_col += start_col; + if (click_row < 0 || click_row >= gp->rows + || click_col < 0 || click_col >= gp->cols) { + return; + } + + const size_t off = gp->line_offset[click_row] + (size_t)click_col; + colnr_T col_from_screen = gp->vcols[off]; + + if (col_from_screen == MAXCOL) { + // When clicking after end of line, still need to set correct curswant + size_t off_l = gp->line_offset[click_row] + (size_t)start_col; + if (gp->vcols[off_l] < MAXCOL) { + // Binary search to find last char in line + size_t off_r = off; + while (off_l < off_r) { + size_t off_m = (off_l + off_r + 1) / 2; + if (gp->vcols[off_m] < MAXCOL) { + off_l = off_m; } else { - // Shouldn't normally happen - *vcolp = MAXCOL; + off_r = off_m - 1; } - } else if (col_from_screen >= 0) { - // Use the virtual column from vcols[], it is accurate also after - // concealed characters. - *vcolp = col_from_screen; } + colnr_T eol_vcol = gp->vcols[off_r]; + assert(eol_vcol < MAXCOL); + if (eol_vcol < 0) { + // Empty line or whole line before w_leftcol, + // with columns before buffer text + eol_vcol = wp->w_leftcol - 1; + } + *vcolp = eol_vcol + (int)(off - off_r); + } else { + // Empty line or whole line before w_leftcol + *vcolp = click_col - start_col + wp->w_leftcol; } + } else if (col_from_screen >= 0) { + // Use the virtual column from vcols[], it is accurate also after + // concealed characters. + *vcolp = col_from_screen; } if (col_from_screen == -2) { diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 9fea6442a9..4097b770c9 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -145,6 +145,7 @@ struct TUIData { } unibi_ext; char *space_buf; bool stopped; + int seen_error_exit; int width; int height; bool rgb; @@ -162,6 +163,7 @@ void tui_start(TUIData **tui_p, int *width, int *height, char **term) tui->is_starting = true; tui->screenshot = NULL; tui->stopped = false; + tui->seen_error_exit = 0; tui->loop = &main_loop; kv_init(tui->invalid_regions); signal_watcher_init(tui->loop, &tui->winch_handle, tui); @@ -384,8 +386,13 @@ static void terminfo_stop(TUIData *tui) unibi_out_ext(tui, tui->unibi_ext.disable_extended_keys); // May restore old title before exiting alternate screen. tui_set_title(tui, (String)STRING_INIT); - // Exit alternate screen. - unibi_out(tui, unibi_exit_ca_mode); + // if nvim exited with nonzero status, without indicated this was an + // intentional exit (like `:1cquit`), it likely was an internal failure. + // Don't clobber the stderr error message in this case. + if (ui_client_exit_status == tui->seen_error_exit) { + // Exit alternate screen. + unibi_out(tui, unibi_exit_ca_mode); + } if (tui->cursor_color_changed) { unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color); } @@ -443,6 +450,11 @@ static void tui_terminal_stop(TUIData *tui) terminfo_stop(tui); } +void tui_error_exit(TUIData *tui, Integer status) +{ + tui->seen_error_exit = (int)status; +} + void tui_stop(TUIData *tui) { tui_terminal_stop(tui); diff --git a/src/nvim/ui_client.h b/src/nvim/ui_client.h index 7e5f847039..05964422f3 100644 --- a/src/nvim/ui_client.h +++ b/src/nvim/ui_client.h @@ -23,6 +23,9 @@ EXTERN sattr_T *grid_line_buf_attr INIT(= NULL); // ID of the ui client channel. If zero, the client is not running. EXTERN uint64_t ui_client_channel_id INIT(= 0); +// exit status from embedded nvim process +EXTERN int ui_client_exit_status INIT(= 0); + // TODO(bfredl): the current structure for how tui and ui_client.c communicate is a bit awkward. // This will be restructured as part of The UI Devirtualization Project. diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index c4449bc201..133ec67942 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -1524,16 +1524,19 @@ describe('API/extmarks', function() sign_hl_group = "Statement", sign_text = ">>", spell = true, - virt_lines = { { { "lines", "Statement" } }}, + virt_lines = { + { { "lines", "Macro" }, { "???" } }, + { { "stack", { "Type", "Search" } }, { "!!!" } }, + }, virt_lines_above = true, virt_lines_leftcol = true, - virt_text = { { "text", "Statement" } }, + virt_text = { { "text", "Macro" }, { "???" }, { "stack", { "Type", "Search" } } }, virt_text_hide = true, virt_text_pos = "right_align", }) set_extmark(ns, marks[2], 0, 0, { priority = 0, - virt_text = { { "text", "Statement" } }, + virt_text = { { "", "Macro" }, { "", { "Type", "Search" } }, { "" } }, virt_text_win_col = 1, }) eq({0, 0, { @@ -1553,10 +1556,13 @@ describe('API/extmarks', function() sign_hl_group = "Statement", sign_text = ">>", spell = true, - virt_lines = { { { "lines", "Statement" } }}, + virt_lines = { + { { "lines", "Macro" }, { "???" } }, + { { "stack", { "Type", "Search" } }, { "!!!" } }, + }, virt_lines_above = true, virt_lines_leftcol = true, - virt_text = { { "text", "Statement" } }, + virt_text = { { "text", "Macro" }, { "???" }, { "stack", { "Type", "Search" } } }, virt_text_hide = true, virt_text_pos = "right_align", } }, get_extmark_by_id(ns, marks[1], { details = true })) @@ -1564,7 +1570,7 @@ describe('API/extmarks', function() ns_id = 1, right_gravity = true, priority = 0, - virt_text = { { "text", "Statement" } }, + virt_text = { { "", "Macro" }, { "", { "Type", "Search" } }, { "" } }, virt_text_hide = false, virt_text_pos = "win_col", virt_text_win_col = 1, diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 44d4470337..6737c2d15b 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -895,13 +895,14 @@ describe('API/win', function() it('includes border', function() local b = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' } local win = meths.open_win(0, true, { - relative='win', row=3, col=3, width=12, height=3, - border = b, + relative='win', row=3, col=3, width=12, height=3, + border = b, }) local cfg = meths.win_get_config(win) eq(b, cfg.border) end) + it('includes border with highlight group', function() local b = { {'a', 'Normal'}, @@ -914,12 +915,25 @@ describe('API/win', function() {'h', 'PreProc'}, } local win = meths.open_win(0, true, { - relative='win', row=3, col=3, width=12, height=3, - border = b, + relative='win', row=3, col=3, width=12, height=3, + border = b, }) local cfg = meths.win_get_config(win) eq(b, cfg.border) end) + + it('includes title and footer', function() + local title = { {'A', {'StatusLine', 'TabLine'}}, {'B'}, {'C', 'WinBar'} } + local footer = { {'A', 'WinBar'}, {'B'}, {'C', {'StatusLine', 'TabLine'}} } + local win = meths.open_win(0, true, { + relative='win', row=3, col=3, width=12, height=3, + border = 'single', title = title, footer = footer, + }) + + local cfg = meths.win_get_config(win) + eq(title, cfg.title) + eq(footer, cfg.footer) + end) end) end) diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index ef87f6c21a..0db9265a29 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -949,6 +949,28 @@ function tests.set_defaults_all_capabilities() } end +function tests.inlay_hint() + skeleton { + on_init = function(params) + local expected_capabilities = protocol.make_client_capabilities() + assert_eq(params.capabilities, expected_capabilities) + return { + capabilities = { + inlayHintProvider = true; + } + } + end; + body = function() + notify('start') + expect_request('textDocument/inlayHint', function() + return nil, {} + end) + expect_notification("finish") + notify('finish') + end; + } +end + -- Tests will be indexed by test_name local test_name = arg[1] local timeout = arg[2] diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 7e30af5058..155c9ad96c 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1244,6 +1244,67 @@ describe('LSP', function() } end) + it('should send correct range for inlay hints with noeol', function() + local expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL, {}, { + method="textDocument/inlayHint", + params = { + textDocument = { + uri = 'file://', + }, + range = { + start = { line = 0, character = 0 }, + ['end'] = { line = 1, character = 3 }, + } + }, + bufnr=2, + client_id=1, + }}; + {NIL, {}, {method="start", client_id=1}}; + } + local client + test_rpc_server { + test_name = "inlay_hint"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + vim.bo[BUFFER].eol = false + ]] + end; + on_init = function(_client) + client = _client + eq(true, client.supports_method('textDocument/inlayHint')) + exec_lua [[ + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) + ]] + end; + on_exit = function(code, signal) + eq(0, code, "exit code") + eq(0, signal, "exit signal") + end; + on_handler = function(err, result, ctx) + if ctx.method == 'start' then + exec_lua [[ + vim.lsp.inlay_hint(BUFFER, true) + ]] + end + if ctx.method == 'textDocument/inlayHint' then + client.notify('finish') + end + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then + client.stop() + end + end; + } + end) + it('should check the body and didChange incremental', function() local expected_handlers = { {NIL, {}, {method="shutdown", client_id=1}}; diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua index 0aa0cdd6d6..e037c9e215 100644 --- a/test/functional/treesitter/highlight_spec.lua +++ b/test/functional/treesitter/highlight_spec.lua @@ -838,3 +838,67 @@ describe('treesitter highlighting (help)', function() end) end) + +describe('treesitter highlighting (nested injections)', function() + local screen + + before_each(function() + screen = Screen.new(80, 7) + screen:attach() + screen:set_default_attr_ids { + [1] = {foreground = Screen.colors.SlateBlue}; + [2] = {bold = true, foreground = Screen.colors.Brown}; + [3] = {foreground = Screen.colors.Cyan4}; + [4] = {foreground = Screen.colors.Fuchsia}; + } + end) + + it("correctly redraws nested injections (GitHub #25252)", function() + insert[=[ +function foo() print("Lua!") end + +local lorem = { + ipsum = {}, + bar = {}, +} +vim.cmd([[ + augroup RustLSP + autocmd CursorHold silent! lua vim.lsp.buf.document_highlight() + augroup END +]]) + ]=] + + exec_lua [[ + vim.opt.scrolloff = 0 + vim.bo.filetype = 'lua' + vim.treesitter.start() + ]] + + -- invalidate the language tree + feed("ggi--[[<ESC>04x") + + screen:expect{grid=[[ + {2:^function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end} | + | + {2:local} {3:lorem} {2:=} {1:{} | + {3:ipsum} {2:=} {1:{},} | + {3:bar} {2:=} {1:{},} | + {1:}} | + | + ]]} + + -- spam newline insert/delete to invalidate Lua > Vim > Lua region + feed("3jo<ESC>ddko<ESC>ddko<ESC>ddko<ESC>ddk0") + + screen:expect{grid=[[ + {2:function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end} | + | + {2:local} {3:lorem} {2:=} {1:{} | + ^ {3:ipsum} {2:=} {1:{},} | + {3:bar} {2:=} {1:{},} | + {1:}} | + | + ]]} + end) + +end) diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 4b92ab730d..6baad28dad 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -1275,6 +1275,7 @@ describe('cmdheight=0', function() it('with multigrid', function() clear{args={'--cmd', 'set cmdheight=0'}} screen:attach{ext_multigrid=true} + meths.buf_set_lines(0, 0, -1, true, {'p'}) screen:expect{grid=[[ ## grid 1 [2:-------------------------]| @@ -1283,7 +1284,7 @@ describe('cmdheight=0', function() [2:-------------------------]| [2:-------------------------]| ## grid 2 - ^ | + ^p | {1:~ }| {1:~ }| {1:~ }| @@ -1302,7 +1303,7 @@ describe('cmdheight=0', function() [2:-------------------------]| [3:-------------------------]| ## grid 2 - | + {6:p} | {1:~ }| {1:~ }| {1:~ }| @@ -1427,7 +1428,21 @@ describe('cmdheight=0', function() | ]]) command('set cmdheight=0') + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {2:[No Name] }| + ]]} command('resize -1') + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {2:[No Name] }| + | + ]]} command('resize +1') screen:expect([[ ^ | diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index d6a6243be2..f378c50792 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -1464,6 +1464,8 @@ describe('extmark decorations', function() end) it('can have virtual text which combines foreground and background groups', function() + screen:try_resize(20, 5) + screen:set_default_attr_ids { [1] = {bold=true, foreground=Screen.colors.Blue}; [2] = {background = tonumber('0x123456'), foreground = tonumber('0xbbbbbb')}; @@ -1481,30 +1483,24 @@ describe('extmark decorations', function() hi VeryBold gui=bold ]] - meths.buf_set_extmark(0, ns, 0, 0, { virt_text={ + insert('##') + local vt = { {'a', {'BgOne', 'FgEin'}}; {'b', {'BgOne', 'FgZwei'}}; {'c', {'BgTwo', 'FgEin'}}; {'d', {'BgTwo', 'FgZwei'}}; {'X', {'BgTwo', 'FgZwei', 'VeryBold'}}; - }}) - - screen:expect{grid=[[ - ^ {2:a}{3:b}{4:c}{5:d}{6:X} | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - | + } + meths.buf_set_extmark(0, ns, 0, 0, { virt_text = vt, virt_text_pos = 'eol' }) + meths.buf_set_extmark(0, ns, 0, 0, { virt_text = vt, virt_text_pos = 'right_align' }) + meths.buf_set_extmark(0, ns, 0, 0, { virt_text = vt, virt_text_pos = 'inline' }) + meths.buf_set_extmark(0, ns, 0, 0, { virt_lines = { vt, vt } }) + screen:expect{grid=[[ + {2:a}{3:b}{4:c}{5:d}{6:X}#^# {2:a}{3:b}{4:c}{5:d}{6:X} {2:a}{3:b}{4:c}{5:d}{6:X}| + {2:a}{3:b}{4:c}{5:d}{6:X} | + {2:a}{3:b}{4:c}{5:d}{6:X} | + {1:~ }| + | ]]} end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index f75fb52108..93ca1aef3f 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -821,7 +821,7 @@ describe('float window', function() [4] = {bold = true, reverse = true}, [5] = {reverse = true}, [6] = {background = Screen.colors.LightMagenta, bold = true, reverse = true}, - [7] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [7] = {foreground = Screen.colors.White, background = Screen.colors.Red}, [8] = {bold = true, foreground = Screen.colors.SeaGreen4}, [9] = {background = Screen.colors.LightGrey, underline = true}, [10] = {background = Screen.colors.LightGrey, underline = true, bold = true, foreground = Screen.colors.Magenta}, @@ -2512,7 +2512,12 @@ describe('float window', function() ]]} end - meths.win_set_config(win, {title= { {"🦄"},{"BB"}},title_pos="right",footer= { {"🦄"},{"BB"}},footer_pos="right"}) + command('hi B0 guibg=Red guifg=Black') + command('hi B1 guifg=White') + meths.win_set_config(win, { + title = {{"🦄"}, {"BB", {"B0", "B1"}}}, title_pos = "right", + footer= {{"🦄"}, {"BB", {"B0", "B1"}}}, footer_pos = "right", + }) if multigrid then screen:expect{grid=[[ ## grid 1 @@ -2533,10 +2538,10 @@ describe('float window', function() ## grid 3 | ## grid 4 - {5:╔═════}🦄BB{5:╗}| + {5:╔═════}🦄{7:BB}{5:╗}| {5:║}{1: halloj! }{5:║}| {5:║}{1: BORDAA }{5:║}| - {5:╚═════}🦄BB{5:╝}| + {5:╚═════}🦄{7:BB}{5:╝}| ]], float_pos={ [4] = { { id = 1001 }, "NW", 1, 2, 5, true } }, win_viewport={ @@ -2547,10 +2552,10 @@ describe('float window', function() screen:expect{grid=[[ ^ | {0:~ }| - {0:~ }{5:╔═════}🦄BB{5:╗}{0: }| + {0:~ }{5:╔═════}🦄{7:BB}{5:╗}{0: }| {0:~ }{5:║}{1: halloj! }{5:║}{0: }| {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| - {0:~ }{5:╚═════}🦄BB{5:╝}{0: }| + {0:~ }{5:╚═════}🦄{7:BB}{5:╝}{0: }| | ]]} end @@ -6287,7 +6292,7 @@ describe('float window', function() end if multigrid then - meths.input_mouse('left', 'press', '', 1, 0, 0) + meths.input_mouse('left', 'press', '', 2, 0, 0) screen:expect{grid=[[ ## grid 1 [2:----------------------------------------]| @@ -6366,7 +6371,7 @@ describe('float window', function() end if multigrid then - meths.input_mouse('left', 'press', '', 1, 0, 0) + meths.input_mouse('left', 'press', '', 2, 0, 0) screen:expect{grid=[[ ## grid 1 [2:----------------------------------------]| @@ -8764,6 +8769,186 @@ describe('float window', function() end end) + it('left click sets correct curswant in float window with border', function() + local buf = meths.create_buf(false,false) + meths.buf_set_lines(buf, 0, -1, true, {'', '', ''}) + meths.open_win(buf, false, {relative='editor', width=20, height=3, row=0, col=5, border='single'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + {5:┌────────────────────┐}| + {5:│}{1: }{5:│}| + {5:│}{1: }{5:│}| + {5:│}{1: }{5:│}| + {5:└────────────────────┘}| + ]], float_pos={ + [4] = {{id = 1001}, "NW", 1, 0, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0}; + }} + else + screen:expect{grid=[[ + ^ {5:┌────────────────────┐} | + {0:~ }{5:│}{1: }{5:│}{0: }| + {0:~ }{5:│}{1: }{5:│}{0: }| + {0:~ }{5:│}{1: }{5:│}{0: }| + {0:~ }{5:└────────────────────┘}{0: }| + {0:~ }| + | + ]]} + end + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 3, 1) + else + meths.input_mouse('left', 'press', '', 0, 3, 6) + end + eq({0, 3, 1, 0, 1}, funcs.getcurpos()) + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 3, 2) + else + meths.input_mouse('left', 'press', '', 0, 3, 7) + end + eq({0, 3, 1, 0, 2}, funcs.getcurpos()) + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 3, 10) + else + meths.input_mouse('left', 'press', '', 0, 3, 15) + end + eq({0, 3, 1, 0, 10}, funcs.getcurpos()) + + command('setlocal foldcolumn=1') + feed('zfkgg') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + {5:┌────────────────────┐}| + {5:│}{19: }{1:^ }{5:│}| + {5:│}{19:+}{28:+-- 2 lines: ·····}{5:│}| + {5:│}{2:~ }{5:│}| + {5:└────────────────────┘}| + ]], float_pos={ + [4] = {{id = 1001}, "NW", 1, 0, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = {id = 1001}, topline = 0, botline = 4, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0}; + }} + else + screen:expect{grid=[[ + {5:┌────────────────────┐} | + {0:~ }{5:│}{19: }{1:^ }{5:│}{0: }| + {0:~ }{5:│}{19:+}{28:+-- 2 lines: ·····}{5:│}{0: }| + {0:~ }{5:│}{2:~ }{5:│}{0: }| + {0:~ }{5:└────────────────────┘}{0: }| + {0:~ }| + | + ]]} + end + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 2, 1) + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + {5:┌────────────────────┐}| + {5:│}{19: }{1:^ }{5:│}| + {5:│}{19:-}{1: }{5:│}| + {5:│}{19:│}{1: }{5:│}| + {5:└────────────────────┘}| + ]], float_pos={ + [4] = {{id = 1001}, "NW", 1, 0, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0}; + }} + else + meths.input_mouse('left', 'press', '', 0, 2, 6) + screen:expect{grid=[[ + {5:┌────────────────────┐} | + {0:~ }{5:│}{19: }{1:^ }{5:│}{0: }| + {0:~ }{5:│}{19:-}{1: }{5:│}{0: }| + {0:~ }{5:│}{19:│}{1: }{5:│}{0: }| + {0:~ }{5:└────────────────────┘}{0: }| + {0:~ }| + | + ]]} + end + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 2, 2) + else + meths.input_mouse('left', 'press', '', 0, 2, 7) + end + eq({0, 2, 1, 0, 1}, funcs.getcurpos()) + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 2, 3) + else + meths.input_mouse('left', 'press', '', 0, 2, 8) + end + eq({0, 2, 1, 0, 2}, funcs.getcurpos()) + + if multigrid then + meths.input_mouse('left', 'press', '', 4, 2, 11) + else + meths.input_mouse('left', 'press', '', 0, 2, 16) + end + eq({0, 2, 1, 0, 10}, funcs.getcurpos()) + end) + it("'winblend' option", function() screen:try_resize(50,9) screen:set_default_attr_ids({ diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index 5e907f82ba..f42682ba69 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -33,7 +33,7 @@ describe("folded lines", function() [1] = {bold = true, foreground = Screen.colors.Blue1}, [2] = {reverse = true}, [3] = {bold = true, reverse = true}, - [4] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [4] = {foreground = Screen.colors.White, background = Screen.colors.Red}, [5] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey}, [6] = {background = Screen.colors.Yellow}, [7] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, @@ -2938,10 +2938,15 @@ describe("folded lines", function() screen:try_resize(30, 7) insert(content1) command("hi! CursorLine guibg=NONE guifg=Red gui=NONE") + command('hi F0 guibg=Red guifg=Black') + command('hi F1 guifg=White') meths.set_option_value('cursorline', true, {}) meths.set_option_value('foldcolumn', '4', {}) - meths.set_option_value('foldtext', - '[[v:folddashes], ["\t", "Search"], [getline(v:foldstart), "NonText"]]', {}) + meths.set_option_value('foldtext', '[' + .. '["▶", ["F0", "F1"]], ' + .. '[v:folddashes], ' + .. '["\t", "Search"], ' + .. '[getline(v:foldstart), "NonText"]]', {}) command('3,4fold') command('5,6fold') @@ -2958,7 +2963,7 @@ describe("folded lines", function() [3:------------------------------]| ## grid 2 {7: }This is a | - {7:+ }{13:^-}{17: }{18:valid English}{13:·····}| + {7:+ }{4:^▶}{13:-}{17: }{18:valid English}{13:·····}| {1:~ }| {1:~ }| {1:~ }| @@ -2969,7 +2974,7 @@ describe("folded lines", function() else screen:expect([[ {7: }This is a | - {7:+ }{13:^-}{17: }{18:valid English}{13:·····}| + {7:+ }{4:^▶}{13:-}{17: }{18:valid English}{13:·····}| {1:~ }| {1:~ }| {1:~ }| @@ -2977,7 +2982,7 @@ describe("folded lines", function() | ]]) end - eq('-\tvalid English', funcs.foldtextresult(2)) + eq('▶-\tvalid English', funcs.foldtextresult(2)) feed('zo') if multigrid then @@ -2993,8 +2998,8 @@ describe("folded lines", function() ## grid 2 {7: }This is a | {7:- }valid English | - {7:│+ }{5:--}{19: }{18:sentence composed }| - {7:│+ }{13:^--}{17: }{18:in his cave.}{13:······}| + {7:│+ }{4:▶}{5:--}{19: }{18:sentence composed }| + {7:│+ }{4:^▶}{13:--}{17: }{18:in his cave.}{13:······}| {1:~ }| {1:~ }| ## grid 3 @@ -3004,15 +3009,15 @@ describe("folded lines", function() screen:expect([[ {7: }This is a | {7:- }valid English | - {7:│+ }{5:--}{19: }{18:sentence composed }| - {7:│+ }{13:^--}{17: }{18:in his cave.}{13:······}| + {7:│+ }{4:▶}{5:--}{19: }{18:sentence composed }| + {7:│+ }{4:^▶}{13:--}{17: }{18:in his cave.}{13:······}| {1:~ }| {1:~ }| | ]]) end - eq('--\tsentence composed by', funcs.foldtextresult(3)) - eq('--\tin his cave.', funcs.foldtextresult(5)) + eq('▶--\tsentence composed by', funcs.foldtextresult(3)) + eq('▶--\tin his cave.', funcs.foldtextresult(5)) command('hi! Visual guibg=Red') feed('V2k') @@ -3029,8 +3034,8 @@ describe("folded lines", function() ## grid 2 {7: }This is a | {7:- }^v{14:alid English} | - {7:│+ }{15:--}{19: }{20:sentence composed }| - {7:│+ }{15:--}{19: }{20:in his cave.}{15:······}| + {7:│+ }{4:▶}{15:--}{19: }{20:sentence composed }| + {7:│+ }{4:▶}{15:--}{19: }{20:in his cave.}{15:······}| {1:~ }| {1:~ }| ## grid 3 @@ -3040,8 +3045,8 @@ describe("folded lines", function() screen:expect([[ {7: }This is a | {7:- }^v{14:alid English} | - {7:│+ }{15:--}{19: }{20:sentence composed }| - {7:│+ }{15:--}{19: }{20:in his cave.}{15:······}| + {7:│+ }{4:▶}{15:--}{19: }{20:sentence composed }| + {7:│+ }{4:▶}{15:--}{19: }{20:in his cave.}{15:······}| {1:~ }| {1:~ }| {11:-- VISUAL LINE --} | @@ -3062,8 +3067,8 @@ describe("folded lines", function() ## grid 2 a si sihT{7: }| {14:hsilgnE dila}^v{7: -}| - {20: desopmoc ecnetnes}{19: }{15:--}{7: +│}| - {15:······}{20:.evac sih ni}{19: }{15:--}{7: +│}| + {20: desopmoc ecnetnes}{19: }{15:--}{4:▶}{7: +│}| + {15:······}{20:.evac sih ni}{19: }{15:--}{4:▶}{7: +│}| {1: ~}| {1: ~}| ## grid 3 @@ -3073,8 +3078,8 @@ describe("folded lines", function() screen:expect([[ a si sihT{7: }| {14:hsilgnE dila}^v{7: -}| - {20: desopmoc ecnetnes}{19: }{15:--}{7: +│}| - {15:······}{20:.evac sih ni}{19: }{15:--}{7: +│}| + {20: desopmoc ecnetnes}{19: }{15:--}{4:▶}{7: +│}| + {15:······}{20:.evac sih ni}{19: }{15:--}{4:▶}{7: +│}| {1: ~}| {1: ~}| {11:-- VISUAL LINE --} | diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index a8d01cfbf1..fd24174f74 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -1676,7 +1676,7 @@ describe('ui/mouse/input', function() end) - it('getmousepos works correctly', function() + it('getmousepos() works correctly', function() local winwidth = meths.get_option_value('winwidth', {}) -- Set winwidth=1 so that window sizes don't change. meths.set_option_value('winwidth', 1, {}) @@ -1771,7 +1771,7 @@ describe('ui/mouse/input', function() -- Test that mouse position values are properly set for ordinary windows. -- Set the float to be unfocusable instead of closing, to additionally test - -- that getmousepos does not consider unfocusable floats. (see discussion + -- that getmousepos() does not consider unfocusable floats. (see discussion -- in PR #14937 for details). opts.focusable = false meths.win_set_config(float, opts) diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index 1778c8218b..6f4082beda 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -2307,6 +2307,7 @@ describe('ext_multigrid', function() {1:~ }| ]]} + -- XXX: mouse_check_grid() doesn't work properly when clicking on grid 1 meths.input_mouse('left', 'press', '', 1, 6, 20) -- TODO(bfredl): "batching" input_mouse is formally not supported yet. -- Normally it should work fine in async context when nvim is not blocked, diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim index 3c21041899..09d3f0432f 100644 --- a/test/old/testdir/test_normal.vim +++ b/test/old/testdir/test_normal.vim @@ -4107,6 +4107,39 @@ func Test_normal_click_on_double_width_char() let &mouse = save_mouse endfunc +func Test_normal_click_on_empty_line() + let save_mouse = &mouse + set mouse=a + botright new + call setline(1, ['', '', '']) + let row = win_screenpos(0)[0] + 2 + 20vsplit + redraw + + call Ntest_setmouse(row, 1) + call feedkeys("\<LeftMouse>", 'xt') + call assert_equal([0, 3, 1, 0, 1], getcurpos()) + call Ntest_setmouse(row, 2) + call feedkeys("\<LeftMouse>", 'xt') + call assert_equal([0, 3, 1, 0, 2], getcurpos()) + call Ntest_setmouse(row, 10) + call feedkeys("\<LeftMouse>", 'xt') + call assert_equal([0, 3, 1, 0, 10], getcurpos()) + + call Ntest_setmouse(row, 21 + 1) + call feedkeys("\<LeftMouse>", 'xt') + call assert_equal([0, 3, 1, 0, 1], getcurpos()) + call Ntest_setmouse(row, 21 + 2) + call feedkeys("\<LeftMouse>", 'xt') + call assert_equal([0, 3, 1, 0, 2], getcurpos()) + call Ntest_setmouse(row, 21 + 10) + call feedkeys("\<LeftMouse>", 'xt') + call assert_equal([0, 3, 1, 0, 10], getcurpos()) + + bwipe! + let &mouse = save_mouse +endfunc + func Test_normal33_g_cmd_nonblank() " Test that g<End> goes to the last non-blank char and g$ to the last " visible column diff --git a/test/old/testdir/test_virtualedit.vim b/test/old/testdir/test_virtualedit.vim index 6ff51e36fb..4ab69d89fe 100644 --- a/test/old/testdir/test_virtualedit.vim +++ b/test/old/testdir/test_virtualedit.vim @@ -564,37 +564,95 @@ func Test_virtualedit_mouse() let save_mouse = &mouse set mouse=a set virtualedit=all - new + botright new + let row = win_screenpos(0)[0] + 20vsplit + wincmd p call setline(1, ["text\tword"]) redraw - call Ntest_setmouse(1, 4) + call Ntest_setmouse(row, 21 + 4) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 4, 0, 4], getcurpos()) - call Ntest_setmouse(1, 5) + call Ntest_setmouse(row, 21 + 5) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 5, 0, 5], getcurpos()) - call Ntest_setmouse(1, 6) + call Ntest_setmouse(row, 21 + 6) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 5, 1, 6], getcurpos()) - call Ntest_setmouse(1, 7) + call Ntest_setmouse(row, 21 + 7) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 5, 2, 7], getcurpos()) - call Ntest_setmouse(1, 8) + call Ntest_setmouse(row, 21 + 8) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 5, 3, 8], getcurpos()) - call Ntest_setmouse(1, 9) + call Ntest_setmouse(row, 21 + 9) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 6, 0, 9], getcurpos()) - call Ntest_setmouse(1, 12) + call Ntest_setmouse(row, 21 + 12) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 9, 0, 12], getcurpos()) - call Ntest_setmouse(1, 13) + call Ntest_setmouse(row, 21 + 13) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 10, 0, 13], getcurpos()) - call Ntest_setmouse(1, 15) + call Ntest_setmouse(row, 21 + 15) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 10, 2, 15], getcurpos()) + + setlocal nowrap + call setline(2, repeat('a', 19)) + normal! j14zl + redraw + call Ntest_setmouse(row, 21 + 1) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 10, 2, 15], getcurpos()) + call Ntest_setmouse(row, 21 + 11) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 10, 12, 25], getcurpos()) + call Ntest_setmouse(row + 1, 21 + 1) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 2, 15, 0, 15], getcurpos()) + call Ntest_setmouse(row + 1, 21 + 11) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 2, 20, 5, 25], getcurpos()) + + setlocal number numberwidth=2 + redraw + call Ntest_setmouse(row, 21 + 3) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 10, 2, 15], getcurpos()) + call Ntest_setmouse(row, 21 + 13) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 10, 12, 25], getcurpos()) + call Ntest_setmouse(row + 1, 21 + 3) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 2, 15, 0, 15], getcurpos()) + call Ntest_setmouse(row + 1, 21 + 13) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 2, 20, 5, 25], getcurpos()) + setlocal nonumber + + if has('signs') + sign define Sign1 text=口 + sign place 1 name=Sign1 line=1 + sign place 2 name=Sign1 line=2 + redraw + call Ntest_setmouse(row, 21 + 3) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 10, 2, 15], getcurpos()) + call Ntest_setmouse(row, 21 + 13) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 10, 12, 25], getcurpos()) + call Ntest_setmouse(row + 1, 21 + 3) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 2, 15, 0, 15], getcurpos()) + call Ntest_setmouse(row + 1, 21 + 13) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 2, 20, 5, 25], getcurpos()) + sign unplace 1 + sign unplace 2 + sign undefine Sign1 + endif bwipe! let &mouse = save_mouse |