diff options
-rw-r--r-- | .github/workflows/test.yml | 2 | ||||
-rw-r--r-- | CMakeLists.txt | 9 | ||||
-rw-r--r-- | cmake/Util.cmake | 13 | ||||
-rw-r--r-- | contrib/luarc.json | 3 | ||||
-rw-r--r-- | runtime/lua/vim/filetype.lua | 3 | ||||
-rw-r--r-- | runtime/lua/vim/lsp.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/_inlay_hint.lua | 61 | ||||
-rw-r--r-- | runtime/pack/dist/opt/termdebug/plugin/termdebug.vim | 55 | ||||
-rw-r--r-- | src/nvim/drawline.c | 31 | ||||
-rw-r--r-- | test/functional/ui/decorations_spec.lua | 58 | ||||
-rw-r--r-- | test/old/testdir/test_filetype.vim | 4 |
11 files changed, 180 insertions, 63 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 14546cb371..bdec96babb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,7 +48,7 @@ jobs: cmake --build .deps - if: success() || failure() && steps.abort_job.outputs.status == 'success' - run: cmake -B build -G Ninja + run: cmake -B build -G Ninja -D CI_LINT=ON - if: "!cancelled()" name: Determine if run should be aborted diff --git a/CMakeLists.txt b/CMakeLists.txt index abd3a57fc0..d0534731b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,11 +229,14 @@ endif() # # Lint # -find_program(SHELLCHECK_PRG shellcheck) -find_program(STYLUA_PRG stylua) +option(CI_LINT "Abort if lint programs not found" OFF) +if(CI_LINT) + set(LINT_REQUIRED "REQUIRED") +endif() +find_program(SHELLCHECK_PRG shellcheck ${LINT_REQUIRED}) +find_program(STYLUA_PRG stylua ${LINT_REQUIRED}) add_glob_target( - REQUIRED TARGET lintlua-luacheck COMMAND ${DEPS_BIN_DIR}/luacheck FLAGS -q diff --git a/cmake/Util.cmake b/cmake/Util.cmake index b70f33a302..0d6fa2a4ce 100644 --- a/cmake/Util.cmake +++ b/cmake/Util.cmake @@ -4,7 +4,6 @@ # depends on the value of TOUCH_STRATEGY. # # Options: -# REQUIRED - Abort if COMMAND doesn't exist. # # Single value arguments: # TARGET - Name of the target @@ -53,7 +52,7 @@ # files. function(add_glob_target) cmake_parse_arguments(ARG - "REQUIRED" + "" "TARGET;COMMAND;GLOB_PAT;TOUCH_STRATEGY" "FLAGS;FILES;GLOB_DIRS;EXCLUDE" ${ARGN} @@ -61,14 +60,8 @@ function(add_glob_target) if(NOT ARG_COMMAND) add_custom_target(${ARG_TARGET}) - if(ARG_REQUIRED) - add_custom_command(TARGET ${ARG_TARGET} - COMMAND ${CMAKE_COMMAND} -E echo "${ARG_TARGET}: ${ARG_COMMAND} not found" - COMMAND false) - else() - add_custom_command(TARGET ${ARG_TARGET} - COMMAND ${CMAKE_COMMAND} -E echo "${ARG_TARGET} SKIP: ${ARG_COMMAND} not found") - endif() + add_custom_command(TARGET ${ARG_TARGET} + COMMAND ${CMAKE_COMMAND} -E echo "${ARG_TARGET} SKIP: ${ARG_COMMAND} not found") return() endif() diff --git a/contrib/luarc.json b/contrib/luarc.json index ebad0581b9..31126e4215 100644 --- a/contrib/luarc.json +++ b/contrib/luarc.json @@ -13,6 +13,9 @@ "teardown", "finally", "lfs" + ], + "disable": [ + "luadoc-miss-see-name" ] }, "workspace": { diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 08a042d376..7bcc67313d 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1372,6 +1372,7 @@ local filename = { ['named.root'] = 'bindzone', WORKSPACE = 'bzl', ['WORKSPACE.bzlmod'] = 'bzl', + BUCK = 'bzl', BUILD = 'bzl', ['cabal.project'] = 'cabalproject', ['cabal.config'] = 'cabalconfig', @@ -2189,7 +2190,7 @@ local pattern = { ['.*/etc/profile'] = function(path, bufnr) return require('vim.filetype.detect').sh(path, M.getlines(bufnr)) end, - ['bash%-fc[%-%.]'] = function(path, bufnr) + ['bash%-fc[%-%.].*'] = function(path, bufnr) return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash') end, ['%.tcshrc.*'] = function(path, bufnr) diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index a8e75c4dc9..1e5ce8fa10 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1183,7 +1183,7 @@ function lsp.start_client(config) --- ---@param code (integer) Error code ---@param err (...) Other arguments may be passed depending on the error kind - ---@see `vim.lsp.rpc.client_errors` for possible errors. Use + ---@see vim.lsp.rpc.client_errors for possible errors. Use ---`vim.lsp.rpc.client_errors[code]` to get a human-friendly name. function dispatch.on_error(code, err) if log.error() then @@ -2366,7 +2366,7 @@ function lsp.formatexpr(opts) } local response = client.request_sync('textDocument/rangeFormatting', params, timeout_ms, bufnr) - if response.result then + if response and response.result then lsp.util.apply_text_edits(response.result, 0, client.offset_encoding) return 0 end diff --git a/runtime/lua/vim/lsp/_inlay_hint.lua b/runtime/lua/vim/lsp/_inlay_hint.lua index 8edf14e707..84794841ae 100644 --- a/runtime/lua/vim/lsp/_inlay_hint.lua +++ b/runtime/lua/vim/lsp/_inlay_hint.lua @@ -8,6 +8,7 @@ local M = {} ---@field client_hint table<integer, table<integer, lsp.InlayHint[]>> client_id -> (lnum -> hints) ---@field enabled boolean Whether inlay hints are enabled for the buffer ---@field timer uv.uv_timer_t? Debounce timer associated with the buffer +---@field applied table<integer, integer> Last version of hints applied to this line ---@type table<integer, lsp._inlay_hint.bufstate> local bufstates = {} @@ -143,6 +144,9 @@ end ---@private local function clear(bufnr) bufnr = resolve_bufnr(bufnr) + if not bufstates[bufnr] then + return + end reset_timer(bufnr) local bufstate = bufstates[bufnr] local client_lens = (bufstate or {}).client_hint or {} @@ -169,7 +173,7 @@ function M.enable(bufnr) bufnr = resolve_bufnr(bufnr) local bufstate = bufstates[bufnr] if not (bufstate and bufstate.enabled) then - bufstates[bufnr] = { enabled = true, timer = nil } + bufstates[bufnr] = { enabled = true, timer = nil, applied = {} } M.refresh({ bufnr = bufnr }) api.nvim_buf_attach(bufnr, true, { on_lines = function(_, cb_bufnr) @@ -183,7 +187,9 @@ function M.enable(bufnr) end, on_reload = function(_, cb_bufnr) clear(cb_bufnr) - bufstates[cb_bufnr] = nil + if bufstates[cb_bufnr] and bufstates[cb_bufnr].enabled then + bufstates[cb_bufnr] = { enabled = true } + end M.refresh({ bufnr = cb_bufnr }) end, on_detach = function(_, cb_bufnr) @@ -238,35 +244,38 @@ api.nvim_set_decoration_provider(namespace, { return end local hints_by_client = bufstate.client_hint - api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1) for lnum = topline, botline do - for _, hints_by_lnum in pairs(hints_by_client) do - local line_hints = hints_by_lnum[lnum] or {} - for _, hint in pairs(line_hints) do - local text = '' - if type(hint.label) == 'string' then - text = hint.label - else - for _, part in ipairs(hint.label) do - text = text .. part.value + if bufstate.applied[lnum] ~= bufstate.version then + api.nvim_buf_clear_namespace(bufnr, namespace, lnum, lnum + 1) + for _, hints_by_lnum in pairs(hints_by_client) do + local line_hints = hints_by_lnum[lnum] or {} + for _, hint in pairs(line_hints) do + local text = '' + if type(hint.label) == 'string' then + text = hint.label + else + for _, part in ipairs(hint.label) do + text = text .. part.value + end end + local vt = {} + if hint.paddingLeft then + vt[#vt + 1] = { ' ' } + end + vt[#vt + 1] = { text, 'LspInlayHint' } + if hint.paddingRight then + vt[#vt + 1] = { ' ' } + end + api.nvim_buf_set_extmark(bufnr, namespace, lnum, hint.position.character, { + virt_text_pos = 'inline', + ephemeral = false, + virt_text = vt, + hl_mode = 'combine', + }) end - local vt = {} - if hint.paddingLeft then - vt[#vt + 1] = { ' ' } - end - vt[#vt + 1] = { text, 'LspInlayHint' } - if hint.paddingRight then - vt[#vt + 1] = { ' ' } - end - api.nvim_buf_set_extmark(bufnr, namespace, lnum, hint.position.character, { - virt_text_pos = 'inline', - ephemeral = false, - virt_text = vt, - hl_mode = 'combine', - }) end + bufstate.applied[lnum] = bufstate.version end end end, diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim index 43c3d7541f..d4b49e85c8 100644 --- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -2,7 +2,7 @@ " " Author: Bram Moolenaar " Copyright: Vim license applies, see ":help license" -" Last Change: 2022 Nov 10 +" Last Change: 2023 Jun 24 " " WORK IN PROGRESS - The basics works stable, more to come " Note: In general you need at least GDB 7.12 because this provides the @@ -87,6 +87,8 @@ func s:Breakpoint2SignNumber(id, subid) return s:break_id + a:id * 1000 + a:subid endfunction +" Define or adjust the default highlighting, using background "new". +" When the 'background' option is set then "old" has the old value. func s:Highlight(init, old, new) let default = a:init ? 'default ' : '' if a:new ==# 'light' && a:old !=# 'light' @@ -96,9 +98,21 @@ func s:Highlight(init, old, new) endif endfunc -call s:Highlight(1, '', &background) -hi default debugBreakpoint term=reverse ctermbg=red guibg=red -hi default debugBreakpointDisabled term=reverse ctermbg=gray guibg=gray +" Define the default highlighting, using the current 'background' value. +func s:InitHighlight() + call s:Highlight(1, '', &background) + hi default debugBreakpoint term=reverse ctermbg=red guibg=red + hi default debugBreakpointDisabled term=reverse ctermbg=gray guibg=gray +endfunc + +" Setup an autocommand to redefine the default highlight when the colorscheme +" is changed. +func s:InitAutocmd() + augroup TermDebug + autocmd! + autocmd ColorScheme * call s:InitHighlight() + augroup END +endfunc " Get the command to execute the debugger as a list, defaults to ["gdb"]. func s:GetCommand() @@ -626,7 +640,7 @@ func s:GdbOutCallback(job_id, msgs, event) for msg in a:msgs if msg =~ '^\^error,msg=' if exists('s:evalexpr') - \ && s:DecodeMessage(msg[11:]) + \ && s:DecodeMessage(msg[11:], v:false) \ =~ 'A syntax error in expression, near\|No symbol .* in current context' " Silently drop evaluation errors. call remove(a:msgs, index) @@ -634,7 +648,7 @@ func s:GdbOutCallback(job_id, msgs, event) continue endif elseif msg[0] == '~' - call add(lines, s:DecodeMessage(msg[1:])) + call add(lines, s:DecodeMessage(msg[1:], v:false)) call remove(a:msgs, index) continue endif @@ -656,21 +670,20 @@ func s:GdbOutCallback(job_id, msgs, event) call s:CommOutput(a:job_id, a:msgs, a:event) endfunc -" Decode a message from gdb. quotedText starts with a ", return the text up +" Decode a message from gdb. "quotedText" starts with a ", return the text up " to the next ", unescaping characters: -" - remove line breaks -" - change \\t to \t +" - remove line breaks (unless "literal" is v:true) +" - change \\t to \t (unless "literal" is v:true) " - change \0xhh to \xhh (disabled for now) " - change \ooo to octal " - change \\ to \ -func s:DecodeMessage(quotedText) +func s:DecodeMessage(quotedText, literal) if a:quotedText[0] != '"' echoerr 'DecodeMessage(): missing quote in ' . a:quotedText return endif - return a:quotedText - \ ->substitute('^"\|".*\|\\n', '', 'g') - \ ->substitute('\\t', "\t", 'g') + let msg = a:quotedText + \ ->substitute('^"\|".*', '', 'g') " multi-byte characters arrive in octal form " NULL-values must be kept encoded as those break the string otherwise \ ->substitute('\\000', s:NullRepl, 'g') @@ -682,6 +695,13 @@ func s:DecodeMessage(quotedText) " \ ->substitute('\\0x00', s:NullRepl, 'g') \ ->substitute('\\\\', '\', 'g') \ ->substitute(s:NullRepl, '\\000', 'g') + if !a:literal + return msg + \ ->substitute('\\t', "\t", 'g') + \ ->substitute('\\n', '', 'g') + else + return msg + endif endfunc const s:NullRepl = 'XXXNULLXXX' @@ -690,7 +710,7 @@ func s:GetFullname(msg) if a:msg !~ 'fullname' return '' endif - let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', '')) + let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', ''), v:true) if has('win32') && name =~ ':\\\\' " sometimes the name arrives double-escaped let name = substitute(name, '\\\\', '\\', 'g') @@ -703,11 +723,11 @@ func s:GetAsmAddr(msg) if a:msg !~ 'addr=' return '' endif - let addr = s:DecodeMessage(substitute(a:msg, '.*addr=', '', '')) + let addr = s:DecodeMessage(substitute(a:msg, '.*addr=', '', ''), v:false) return addr endfunc -function s:EndTermDebug(job_id, exit_code, event) +func s:EndTermDebug(job_id, exit_code, event) let s:running = v:false if s:starting return @@ -1664,5 +1684,8 @@ func s:BufUnloaded() endfor endfunc +call s:InitHighlight() +call s:InitAutocmd() + let &cpo = s:keepcpo unlet s:keepcpo diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 9728804ed7..b8dc1297cc 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -134,6 +134,7 @@ typedef struct { int skip_cells; // nr of cells to skip for virtual text int skipped_cells; // nr of skipped virtual text cells + bool more_virt_inline_chunks; // indicates if there is more inline virtual text after n_extra } winlinevars_T; /// for line_putchar. Contains the state that needs to be remembered from @@ -870,6 +871,25 @@ static void apply_cursorline_highlight(win_T *wp, winlinevars_T *wlv) } } +// Checks if there is more inline virtual text that need to be drawn +// and sets has_more_virt_inline_chunks to reflect that. +static bool has_more_inline_virt(winlinevars_T *wlv, ptrdiff_t v) +{ + DecorState *state = &decor_state; + for (size_t i = 0; i < kv_size(state->active); i++) { + DecorRange *item = &kv_A(state->active, i); + if (item->start_row != state->row + || !kv_size(item->decor.virt_text) + || item->decor.virt_text_pos != kVTInline) { + continue; + } + if (item->draw_col >= -1 && item->start_col >= v) { + return true; + } + } + return false; +} + static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t v) { while (wlv->n_extra == 0) { @@ -892,6 +912,7 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t break; } } + wlv->more_virt_inline_chunks = has_more_inline_virt(wlv, v); if (!kv_size(wlv->virt_inline)) { // no more inline virtual text here break; @@ -909,6 +930,11 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t wlv->c_final = NUL; wlv->extra_attr = vtc.hl_id ? syn_id2attr(vtc.hl_id) : 0; wlv->n_attr = mb_charlen(vtc.text); + + // Checks if there is more inline virtual text chunks that need to be drawn. + wlv->more_virt_inline_chunks = has_more_inline_virt(wlv, v) + || wlv->virt_inline_i < kv_size(wlv->virt_inline); + // If the text didn't reach until the first window // column we need to skip cells. if (wlv->skip_cells > 0) { @@ -2874,7 +2900,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl && !has_fold && (*ptr != NUL || lcs_eol_one > 0 - || (wlv.n_extra > 0 && (wlv.c_extra != NUL || *wlv.p_extra != NUL)))) { + || (wlv.n_extra > 0 && (wlv.c_extra != NUL || *wlv.p_extra != NUL)) + || wlv.more_virt_inline_chunks)) { c = wp->w_p_lcs_chars.ext; wlv.char_attr = win_hl_attr(wp, HLF_AT); mb_c = c; @@ -3064,7 +3091,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL && wlv.p_extra != at_end_str) || (wlv.n_extra != 0 - && (wlv.c_extra != NUL || *wlv.p_extra != NUL)))) { + && (wlv.c_extra != NUL || *wlv.p_extra != NUL)) || wlv.more_virt_inline_chunks)) { bool wrap = wp->w_p_wrap // Wrapping enabled. && wlv.filler_todo <= 0 // Not drawing diff filler lines. && lcs_eol_one != -1 // Haven't printed the lcs_eol character. diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 5de0106a8c..4c04bad3a0 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -2506,6 +2506,64 @@ bbbbbbb]]) | ]]} end) + + it('does not crash at right edge of wide window #23848', function() + screen:try_resize(82, 5) + meths.buf_set_extmark(0, ns, 0, 0, {virt_text = {{('a'):rep(82)}, {'b'}}, virt_text_pos = 'inline'}) + screen:expect{grid=[[ + ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + b | + {1:~ }| + {1:~ }| + | + ]]} + command('set nowrap') + screen:expect{grid=[[ + ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + feed('82i0<Esc>0') + screen:expect{grid=[[ + ^0000000000000000000000000000000000000000000000000000000000000000000000000000000000| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + command('set wrap') + screen:expect{grid=[[ + ^0000000000000000000000000000000000000000000000000000000000000000000000000000000000| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + b | + {1:~ }| + | + ]]} + end) + + it('list "extends" is drawn with only inline virtual text offscreen', function() + command('set nowrap') + command('set list') + command('set listchars+=extends:c') + meths.buf_set_extmark(0, ns, 0, 0, + { virt_text = { { 'test', 'Special' } }, virt_text_pos = 'inline' }) + insert(string.rep('a', 50)) + feed('gg0') + screen:expect { grid = [[ + ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{1:c}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end) end) describe('decorations: virtual lines', function() diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index fe23bd73b7..8004c35d97 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -535,7 +535,7 @@ let s:filename_checks = { \ 'services': ['/etc/services', 'any/etc/services'], \ 'setserial': ['/etc/serial.conf', 'any/etc/serial.conf'], \ 'sexplib': ['file.sexp'], - \ 'sh': ['.bashrc', 'file.bash', '/usr/share/doc/bash-completion/filter.sh','/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf'], + \ 'sh': ['.bashrc', '.bash_profile', '.bash-profile', '.bash_logout', '.bash-logout', '.bash_aliases', '.bash-aliases', '/tmp/bash-fc-3Ozjlw', '/tmp/bash-fc.3Ozjlw', 'PKGBUILD', 'APKBUILD', 'file.bash', '/usr/share/doc/bash-completion/filter.sh', '/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf'], \ 'sieve': ['file.siv', 'file.sieve'], \ 'sil': ['file.sil'], \ 'simula': ['file.sim'], @@ -705,7 +705,7 @@ let s:filename_checks = { let s:filename_case_checks = { \ 'modula2': ['file.DEF'], - \ 'bzl': ['file.BUILD', 'BUILD'], + \ 'bzl': ['file.BUILD', 'BUILD', 'BUCK'], \ } func CheckItems(checks) |