diff options
39 files changed, 458 insertions, 103 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 23019428c8..ba696ad85a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,14 +13,14 @@ concurrency: cancel-in-progress: true env: - ASAN_OPTIONS: detect_leaks=1:check_initialization_order=1:handle_abort=1:handle_sigill=1:log_path=${{ github.workspace }}/build/log/asan:intercept_tls_get_addr=0 + ASAN_OPTIONS: detect_leaks=1:check_initialization_order=1:log_path=${{ github.workspace }}/build/log/asan:intercept_tls_get_addr=0 BIN_DIR: ${{ github.workspace }}/bin BUILD_DIR: ${{ github.workspace }}/build INSTALL_PREFIX: ${{ github.workspace }}/nvim-install LOG_DIR: ${{ github.workspace }}/build/log NVIM_LOG_FILE: ${{ github.workspace }}/build/.nvimlog TSAN_OPTIONS: log_path=${{ github.workspace }}/build/log/tsan - UBSAN_OPTIONS: "print_stacktrace=1 log_path=${{ github.workspace }}/build/log/ubsan" + UBSAN_OPTIONS: log_path=${{ github.workspace }}/build/log/ubsan VALGRIND_LOG: ${{ github.workspace }}/build/log/valgrind-%p.log # TEST_FILE: test/functional/core/startup_spec.lua # TEST_FILTER: foo diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 85dc0fdbfd..4c8387fbd2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -187,7 +187,7 @@ master build. To view the defects, just request access; you will be approved. ``` - When running Neovim, use ``` - UBSAN_OPTIONS=print_stacktrace=1 ASAN_OPTIONS=log_path=/tmp/nvim_asan,handle_abort=1,handle_sigill=1 nvim args... + ASAN_OPTIONS=log_path=/tmp/nvim_asan nvim args... ``` - If Neovim exits unexpectedly, check `/tmp/nvim_asan.{PID}` (or your preferred `log_path`) for log files with error messages. diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index 5c9d29bea6..385ede0e42 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -52,8 +52,8 @@ LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178 TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.20.2.tar.gz TREESITTER_C_SHA256 af66fde03feb0df4faf03750102a0d265b007e5d957057b6b293c13116a70af2 -TREESITTER_LUA_URL https://github.com/MunifTanjim/tree-sitter-lua/archive/v0.0.14.tar.gz -TREESITTER_LUA_SHA256 930d0370dc15b66389869355c8e14305b9ba7aafd36edbfdb468c8023395016d +TREESITTER_LUA_URL https://github.com/MunifTanjim/tree-sitter-lua/archive/v0.0.17.tar.gz +TREESITTER_LUA_SHA256 8963fd0a185d786c164dfca3824941c7eaec497ce49a3a0bc24bf753f5e0e59c TREESITTER_VIM_URL https://github.com/neovim/tree-sitter-vim/archive/v0.3.0.tar.gz TREESITTER_VIM_SHA256 403acec3efb7cdb18ff3d68640fc823502a4ffcdfbb71cec3f98aa786c21cbe2 TREESITTER_VIMDOC_URL https://github.com/neovim/tree-sitter-vimdoc/archive/v2.0.0.tar.gz diff --git a/contrib/asan.sh b/contrib/asan.sh index baf7abb5a8..6354d65514 100755 --- a/contrib/asan.sh +++ b/contrib/asan.sh @@ -13,9 +13,6 @@ export CC='clang' # Change to detect_leaks=1 to detect memory leaks (slower). export ASAN_OPTIONS="detect_leaks=0:log_path=$log_path/asan" -# Show backtraces in the logs. -export UBSAN_OPTIONS="print_stacktrace=1" - make -C "$root_path" CMAKE_EXTRA_FLAGS="-DENABLE_ASAN_UBSAN=ON" VIMRUNTIME="$root_path"/runtime "$root_path"/build/bin/nvim diff --git a/contrib/flake.nix b/contrib/flake.nix index d38d1fd2b7..59d977b748 100644 --- a/contrib/flake.nix +++ b/contrib/flake.nix @@ -106,7 +106,6 @@ # ASAN_OPTIONS=detect_leaks=1 export ASAN_OPTIONS="log_path=./test.log:abort_on_error=1" - export UBSAN_OPTIONS=print_stacktrace=1 # for treesitter functionaltests mkdir -p runtime/parser diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 483812b7c9..64b4730eee 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -221,8 +221,7 @@ The following predicates are built in: similar to `match?` `contains?` *treesitter-predicate-contains?* - Match a string against parts of the text corresponding to a node: - >query + Match a string against parts of the text corresponding to a node: >query ((identifier) @foo (#contains? @foo "foo")) ((identifier) @foo-bar (#contains? @foo-bar "foo" "bar")) < @@ -234,6 +233,19 @@ The following predicates are built in: This is the recommended way to check if the node matches one of many keywords, as it has been optimized for this. + `has-ancestor?` *treesitter-predicate-has-ancestor?* + Match any of the given node types against all ancestors of a node: >query + ((identifier) @variable.builtin + (#any-of? @variable.builtin "begin" "end") + (#has-ancestor? @variable.builtin range_expression)) +< + `has-parent?` *treesitter-predicate-has-parent?* + Match any of the given node types against the direct ancestor of a + node: >query + (((field_expression + (field_identifier) @method)) @_parent + (#has-parent? @_parent template_method function_declarator)) +< *lua-treesitter-not-predicate* Each predicate has a `not-` prefixed predicate that is just the negation of the predicate. diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 2a51bde263..7f9f3a2bce 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -73,10 +73,14 @@ function M.basename(file) end ---@private -local function join_paths(...) +---@param ... string +---@return string +function M._join_paths(...) return (table.concat({ ... }, '/'):gsub('//+', '/')) end +local join_paths = M._join_paths + ---@alias Iterator fun(): string?, string? --- Return an iterator over the files and directories located in {path} diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index c1009f5f5d..9ca4b560c6 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -45,6 +45,8 @@ function TSNode:_rawquery(query, captures, start, end_, opts) end ---@return fun(): string, any function TSNode:_rawquery(query, captures, start, end_, opts) end +---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string) + ---@class TSParser ---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean?): TSTree, integer[] ---@field reset fun(self: TSParser) @@ -52,6 +54,8 @@ function TSNode:_rawquery(query, captures, start, end_, opts) end ---@field set_included_ranges fun(self: TSParser, ranges: Range6[]) ---@field set_timeout fun(self: TSParser, timeout: integer) ---@field timeout fun(self: TSParser): integer +---@field _set_logger fun(self: TSParser, lex: boolean, parse: boolean, cb: TSLoggerCallback) +---@field _logger fun(self: TSParser): TSLoggerCallback ---@class TSTree ---@field root fun(self: TSTree): TSNode diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua index f4db5016ac..35081c6400 100644 --- a/runtime/lua/vim/treesitter/_range.lua +++ b/runtime/lua/vim/treesitter/_range.lua @@ -143,6 +143,29 @@ function M.contains(r1, r2) return true end +--- @param source integer|string +--- @param index integer +--- @return integer +local function get_offset(source, index) + if index == 0 then + return 0 + end + + if type(source) == 'number' then + return api.nvim_buf_get_offset(source, index) + end + + local byte = 0 + local next_offset = source:gmatch('()\n') + local line = 1 + while line <= index do + byte = next_offset() --[[@as integer]] + line = line + 1 + end + + return byte +end + ---@private ---@param source integer|string ---@param range Range @@ -152,19 +175,10 @@ function M.add_bytes(source, range) return range --[[@as Range6]] end - local start_row, start_col, end_row, end_col = range[1], range[2], range[3], range[4] - local start_byte = 0 - local end_byte = 0 + local start_row, start_col, end_row, end_col = M.unpack4(range) -- TODO(vigoux): proper byte computation here, and account for EOL ? - if type(source) == 'number' then - -- Easy case, this is a buffer parser - start_byte = api.nvim_buf_get_offset(source, start_row) + start_col - end_byte = api.nvim_buf_get_offset(source, end_row) + end_col - elseif type(source) == 'string' then - -- string parser, single `\n` delimited string - start_byte = vim.fn.byteidx(source, start_col) - end_byte = vim.fn.byteidx(source, end_col) - end + local start_byte = get_offset(source, start_row) + start_col + local end_byte = get_offset(source, end_row) + end_col return { start_row, start_col, start_byte, end_row, end_col, end_byte } end diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 0efe3af85c..6c780f33c4 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -31,8 +31,17 @@ --- shouldn't be done directly in the change callback anyway as they will be very frequent. Rather --- a plugin that does any kind of analysis on a tree should use a timer to throttle too frequent --- updates. +--- + +-- Debugging: +-- +-- vim.g.__ts_debug levels: +-- - 1. Messages from languagetree.lua +-- - 2. Parse messages from treesitter +-- - 2. Lex messages from treesitter +-- +-- Log file can be found in stdpath('log')/treesitter.log -local api = vim.api local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') local Range = require('vim.treesitter._range') @@ -75,6 +84,8 @@ local TSCallbackNames = { ---@field private _source (integer|string) Buffer or string to parse ---@field private _trees TSTree[] Reference to parsed tree (one for each language) ---@field private _valid boolean|table<integer,boolean> If the parsed tree is valid +---@field private _logger? fun(logtype: string, msg: string) +---@field private _logfile? file* local LanguageTree = {} ---@class LanguageTreeOpts @@ -114,6 +125,10 @@ function LanguageTree.new(source, lang, opts) _callbacks_rec = {}, }, LanguageTree) + if vim.g.__ts_debug and type(vim.g.__ts_debug) == 'number' then + self:_set_logger() + end + for _, name in pairs(TSCallbackNames) do self._callbacks[name] = {} self._callbacks_rec[name] = {} @@ -122,6 +137,33 @@ function LanguageTree.new(source, lang, opts) return self end +function LanguageTree:_set_logger() + local source = self:source() + source = type(source) == 'string' and 'text' or tostring(source) + + local lang = self:lang() + + local logfilename = vim.fs._join_paths(vim.fn.stdpath('log'), 'treesitter.log') + + local logfile, openerr = io.open(logfilename, 'a+') + + if not logfile or openerr then + error(string.format('Could not open file (%s) for logging: %s', logfilename, openerr)) + return + end + + self._logfile = logfile + + self._logger = function(logtype, msg) + self._logfile:write(string.format('%s:%s:(%s) %s\n', source, lang, logtype, msg)) + self._logfile:flush() + end + + local log_lex = vim.g.__ts_debug >= 3 + local log_parse = vim.g.__ts_debug >= 2 + self._parser:_set_logger(log_lex, log_parse, self._logger) +end + ---@private ---Measure execution time of a function ---@generic R1, R2, R3 @@ -139,7 +181,11 @@ end ---@private ---@vararg any function LanguageTree:_log(...) - if vim.g.__ts_debug == nil then + if not self._logger then + return + end + + if not vim.g.__ts_debug or vim.g.__ts_debug < 1 then return end @@ -150,19 +196,17 @@ function LanguageTree:_log(...) local info = debug.getinfo(2, 'nl') local nregions = #self:included_regions() - local prefix = - string.format('%s:%d: [%s:%d] ', info.name, info.currentline, self:lang(), nregions) + local prefix = string.format('%s:%d: (#regions=%d) ', info.name, info.currentline, nregions) - api.nvim_out_write(prefix) + local msg = { prefix } for _, x in ipairs(args) do if type(x) == 'string' then - api.nvim_out_write(x) + msg[#msg + 1] = x else - api.nvim_out_write(vim.inspect(x, { newline = ' ', indent = '' })) + msg[#msg + 1] = vim.inspect(x, { newline = ' ', indent = '' }) end - api.nvim_out_write(' ') end - api.nvim_out_write('\n') + self._logger('nvim', table.concat(msg, ' ')) end --- Invalidates this parser and all its children @@ -876,6 +920,11 @@ end function LanguageTree:_on_detach(...) self:invalidate(true) self:_do_callback('detach', ...) + if self._logfile then + self._logger('nvim', 'detaching') + self._logger = nil + self._logfile:close() + end end --- Registers callbacks for the |LanguageTree|. diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 75e5bf8870..73b561c777 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -382,6 +382,39 @@ local predicate_handlers = { return string_set[node_text] end, + + ['has-ancestor?'] = function(match, _, _, predicate) + local node = match[predicate[2]] + if not node then + return true + end + + local ancestor_types = {} + for _, type in ipairs({ unpack(predicate, 3) }) do + ancestor_types[type] = true + end + + node = node:parent() + while node do + if ancestor_types[node:type()] then + return true + end + node = node:parent() + end + return false + end, + + ['has-parent?'] = function(match, _, _, predicate) + local node = match[predicate[2]] + if not node then + return true + end + + if vim.list_contains({ unpack(predicate, 3) }, node:parent():type()) then + return true + end + return false + end, } -- As we provide lua-match? also expose vim-match? diff --git a/runtime/queries/c/highlights.scm b/runtime/queries/c/highlights.scm index dee70f9cc7..523a792403 100644 --- a/runtime/queries/c/highlights.scm +++ b/runtime/queries/c/highlights.scm @@ -117,12 +117,19 @@ (preproc_defined) ] @function.macro -(field_identifier) @property +(((field_expression + (field_identifier) @property)) @_parent + (#not-has-parent? @_parent template_method function_declarator call_expression)) + +(field_designator) @property +(((field_identifier) @property) + (#has-ancestor? @property field_declaration) + (#not-has-ancestor? @property function_declarator)) + (statement_identifier) @label [ (type_identifier) - (sized_type_specifier) (type_descriptor) ] @type @@ -138,6 +145,8 @@ (primitive_type) @type.builtin +(sized_type_specifier _ @type.builtin type: _?) + ((identifier) @constant (#lua-match? @constant "^[A-Z][A-Z0-9_]+$")) (enumerator @@ -163,6 +172,10 @@ field: (field_identifier) @function.call)) (function_declarator declarator: (identifier) @function) +(function_declarator + declarator: (parenthesized_declarator + (pointer_declarator + declarator: (field_identifier) @function))) (preproc_function_def name: (identifier) @function.macro) diff --git a/runtime/queries/lua/highlights.scm b/runtime/queries/lua/highlights.scm index 5fbf8a1833..537a171441 100644 --- a/runtime/queries/lua/highlights.scm +++ b/runtime/queries/lua/highlights.scm @@ -127,8 +127,14 @@ (identifier) @variable +((identifier) @constant.builtin + (#eq? @constant.builtin "_VERSION")) + ((identifier) @variable.builtin - (#any-of? @variable.builtin "_G" "_VERSION" "debug" "io" "jit" "math" "os" "package" "self" "string" "table" "utf8")) + (#eq? @variable.builtin "self")) + +((identifier) @namespace.builtin + (#any-of? @namespace.builtin "_G" "debug" "io" "jit" "math" "os" "package" "string" "table" "utf8")) ((identifier) @keyword.coroutine (#eq? @keyword.coroutine "coroutine")) @@ -210,5 +216,7 @@ (string) @string @spell +(escape_sequence) @string.escape + ;; Error (ERROR) @error diff --git a/runtime/queries/lua/injections.scm b/runtime/queries/lua/injections.scm index 1c7bdaf951..3fcebe83f3 100644 --- a/runtime/queries/lua/injections.scm +++ b/runtime/queries/lua/injections.scm @@ -13,6 +13,14 @@ (#set! injection.language "vim") (#any-of? @_vimcmd_identifier "vim.cmd" "vim.api.nvim_command" "vim.api.nvim_exec2" "vim.api.nvim_cmd")) +; vim.rcprequest(123, "nvim_exec_lua", "return vim.api.nvim_buf_get_lines(0, 0, -1, false)", false) +((function_call + name: (_) @_vimcmd_identifier + arguments: (arguments . (_) . (string content: _ @_method) . (string content: _ @injection.content))) + (#set! injection.language "lua") + (#any-of? @_vimcmd_identifier "vim.rpcrequest" "vim.rpcnotify") + (#eq? @_method "nvim_exec_lua")) + ((function_call name: (_) @_vimcmd_identifier arguments: (arguments (string content: _ @injection.content) .)) @@ -20,7 +28,7 @@ (#any-of? @_vimcmd_identifier "vim.treesitter.query.set" "vim.treesitter.query.parse")) ;; highlight string as query if starts with `;; query` -((string ("string_content") @injection.content) +(string content: _ @injection.content (#set! injection.language "query") (#lua-match? @injection.content "^%s*;+%s?query")) diff --git a/runtime/queries/vim/highlights.scm b/runtime/queries/vim/highlights.scm index ce25b13b9f..09188ddb68 100644 --- a/runtime/queries/vim/highlights.scm +++ b/runtime/queries/vim/highlights.scm @@ -275,7 +275,7 @@ ; Options ((set_value) @number - (#match? @number "^[0-9]+(\.[0-9]+)?$")) + (#lua-match? @number "^[%d]+(%.[%d]+)?$")) (inv_option "!" @operator) (set_item "?" @operator) diff --git a/runtime/queries/vimdoc/highlights.scm b/runtime/queries/vimdoc/highlights.scm index c0d88301bc..e0dce49b2a 100644 --- a/runtime/queries/vimdoc/highlights.scm +++ b/runtime/queries/vimdoc/highlights.scm @@ -1,7 +1,7 @@ -(h1) @text.title -(h2) @text.title -(h3) @text.title -(column_heading) @text.title +(h1) @text.title.1 +(h2) @text.title.2 +(h3) @text.title.3 +(column_heading) @text.title.4 (column_heading "~" @conceal (#set! conceal "")) (tag diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 325b376b30..070cea3b59 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -836,6 +836,7 @@ if(ENABLE_ASAN_UBSAN) -fsanitize=address -fsanitize=undefined) target_link_libraries(nvim PRIVATE -fsanitize=address -fsanitize=undefined) + target_compile_definitions(nvim PRIVATE ENABLE_ASAN_UBSAN) elseif(ENABLE_MSAN) message(STATUS "Enabling memory sanitizer for nvim.") target_compile_options(nvim PRIVATE diff --git a/src/nvim/README.md b/src/nvim/README.md index cbd5daba4e..75155fb9c6 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -71,9 +71,8 @@ Create a directory to store logs: Configure the sanitizer(s) via these environment variables: # Change to detect_leaks=1 to detect memory leaks (slower, noisier). - export ASAN_OPTIONS="detect_leaks=0:log_path=$HOME/logs/asan,handle_abort=1,handle_sigill=1" + export ASAN_OPTIONS="detect_leaks=0:log_path=$HOME/logs/asan" # Show backtraces in the logs. - export UBSAN_OPTIONS=print_stacktrace=1 export MSAN_OPTIONS="log_path=${HOME}/logs/msan" export TSAN_OPTIONS="log_path=${HOME}/logs/tsan" diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index e734a340d9..49fd09ebd0 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1084,11 +1084,11 @@ char *do_bufdel(int command, char *arg, int addr_count, int start_bnr, int end_b if (deleted == 0) { if (command == DOBUF_UNLOAD) { - STRCPY(IObuff, _("E515: No buffers were unloaded")); + xstrlcpy(IObuff, _("E515: No buffers were unloaded"), IOSIZE); } else if (command == DOBUF_DEL) { - STRCPY(IObuff, _("E516: No buffers were deleted")); + xstrlcpy(IObuff, _("E516: No buffers were deleted"), IOSIZE); } else { - STRCPY(IObuff, _("E517: No buffers were wiped out")); + xstrlcpy(IObuff, _("E517: No buffers were wiped out"), IOSIZE); } errormsg = IObuff; } else if (deleted >= p_report) { diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c index 84c1276b8b..cfa3ea5bf3 100644 --- a/src/nvim/bufwrite.c +++ b/src/nvim/bufwrite.c @@ -745,7 +745,7 @@ static int buf_write_make_backup(char *fname, bool append, FileInfo *file_info_o // the ones from the original file. // First find a file name that doesn't exist yet (use some // arbitrary numbers). - STRCPY(IObuff, fname); + xstrlcpy(IObuff, fname, IOSIZE); for (int i = 4913;; i += 123) { char *tail = path_tail(IObuff); size_t size = (size_t)(tail - IObuff); @@ -1749,24 +1749,24 @@ restore_backup: add_quoted_fname(IObuff, IOSIZE, buf, fname); bool insert_space = false; if (write_info.bw_conv_error) { - STRCAT(IObuff, _(" CONVERSION ERROR")); + xstrlcat(IObuff, _(" CONVERSION ERROR"), IOSIZE); insert_space = true; if (write_info.bw_conv_error_lnum != 0) { vim_snprintf_add(IObuff, IOSIZE, _(" in line %" PRId64 ";"), (int64_t)write_info.bw_conv_error_lnum); } } else if (notconverted) { - STRCAT(IObuff, _("[NOT converted]")); + xstrlcat(IObuff, _("[NOT converted]"), IOSIZE); insert_space = true; } else if (converted) { - STRCAT(IObuff, _("[converted]")); + xstrlcat(IObuff, _("[converted]"), IOSIZE); insert_space = true; } if (device) { - STRCAT(IObuff, _("[Device]")); + xstrlcat(IObuff, _("[Device]"), IOSIZE); insert_space = true; } else if (newfile) { - STRCAT(IObuff, new_file_message()); + xstrlcat(IObuff, new_file_message(), IOSIZE); insert_space = true; } if (no_eol) { @@ -1780,9 +1780,9 @@ restore_backup: msg_add_lines(insert_space, (long)lnum, nchars); // add line/char count if (!shortmess(SHM_WRITE)) { if (append) { - STRCAT(IObuff, shortmess(SHM_WRI) ? _(" [a]") : _(" appended")); + xstrlcat(IObuff, shortmess(SHM_WRI) ? _(" [a]") : _(" appended"), IOSIZE); } else { - STRCAT(IObuff, shortmess(SHM_WRI) ? _(" [w]") : _(" written")); + xstrlcat(IObuff, shortmess(SHM_WRI) ? _(" [w]") : _(" written"), IOSIZE); } } diff --git a/src/nvim/cmdhist.c b/src/nvim/cmdhist.c index 81b93e5304..fc84cecc1a 100644 --- a/src/nvim/cmdhist.c +++ b/src/nvim/cmdhist.c @@ -641,9 +641,10 @@ void ex_history(exarg_T *eap) } for (; !got_int && histype1 <= histype2; histype1++) { - STRCPY(IObuff, "\n # "); + xstrlcpy(IObuff, "\n # ", IOSIZE); assert(history_names[histype1] != NULL); - STRCAT(STRCAT(IObuff, history_names[histype1]), " history"); + xstrlcat(IObuff, history_names[histype1], IOSIZE); + xstrlcat(IObuff, " history", IOSIZE); msg_puts_title(IObuff); int idx = hisidx[histype1]; histentry_T *hist = history[histype1]; @@ -669,7 +670,7 @@ void ex_history(exarg_T *eap) trunc_string(hist[i].hisstr, IObuff + strlen(IObuff), Columns - 10, IOSIZE - (int)strlen(IObuff)); } else { - STRCAT(IObuff, hist[i].hisstr); + xstrlcat(IObuff, hist[i].hisstr, IOSIZE); } msg_outtrans(IObuff); } diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 7e20a298dd..a52b8d3f18 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2892,9 +2892,9 @@ char *get_user_func_name(expand_T *xp, int idx) cat_func_name(IObuff, IOSIZE, fp); if (xp->xp_context != EXPAND_USER_FUNC) { - STRCAT(IObuff, "("); + xstrlcat(IObuff, "(", IOSIZE); if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args)) { - STRCAT(IObuff, ")"); + xstrlcat(IObuff, ")", IOSIZE); } } return IObuff; @@ -3505,7 +3505,7 @@ char *get_return_cmd(void *rettv) s = ""; } - STRCPY(IObuff, ":return "); + xstrlcpy(IObuff, ":return ", IOSIZE); xstrlcpy(IObuff + 8, s, IOSIZE - 8); if (strlen(s) + 8 >= IOSIZE) { STRCPY(IObuff + IOSIZE - 4, "..."); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 83232d5f17..9666d80de2 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1454,7 +1454,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er } // Fail if command is invalid if (eap->cmdidx == CMD_SIZE) { - STRCPY(IObuff, _(e_not_an_editor_command)); + xstrlcpy(IObuff, _(e_not_an_editor_command), IOSIZE); // If the modifier was parsed OK the error must be in the following command char *cmdname = after_modifier ? after_modifier : cmdline; append_command(cmdname); @@ -2044,7 +2044,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter // Check for wrong commands. if (ea.cmdidx == CMD_SIZE) { if (!ea.skip) { - STRCPY(IObuff, _(e_not_an_editor_command)); + xstrlcpy(IObuff, _(e_not_an_editor_command), IOSIZE); // If the modifier was parsed OK the error must be in the following // command char *cmdname = after_modifier ? after_modifier : *cmdlinep; @@ -2321,7 +2321,7 @@ doend: if (errormsg != NULL && *errormsg != NUL && !did_emsg) { if (flags & DOCMD_VERBOSE) { if (errormsg != IObuff) { - STRCPY(IObuff, errormsg); + xstrlcpy(IObuff, errormsg, IOSIZE); errormsg = IObuff; } append_command(*ea.cmdlinep); @@ -2888,7 +2888,7 @@ static void append_command(char *cmd) d -= utf_head_off(IObuff, d); STRCPY(d, "..."); } - STRCAT(IObuff, ": "); + xstrlcat(IObuff, ": ", IOSIZE); d = IObuff + strlen(IObuff); while (*s != NUL && d - IObuff + 5 < IOSIZE) { if ((uint8_t)s[0] == 0xc2 && (uint8_t)s[1] == 0xa0) { diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index d659987686..57aa063504 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -1693,22 +1693,22 @@ failed: #ifdef UNIX if (S_ISFIFO(perm)) { // fifo - STRCAT(IObuff, _("[fifo]")); + xstrlcat(IObuff, _("[fifo]"), IOSIZE); c = true; } if (S_ISSOCK(perm)) { // or socket - STRCAT(IObuff, _("[socket]")); + xstrlcat(IObuff, _("[socket]"), IOSIZE); c = true; } # ifdef OPEN_CHR_FILES if (S_ISCHR(perm)) { // or character special - STRCAT(IObuff, _("[character special]")); + xstrlcat(IObuff, _("[character special]"), IOSIZE); c = true; } # endif #endif if (curbuf->b_p_ro) { - STRCAT(IObuff, shortmess(SHM_RO) ? _("[RO]") : _("[readonly]")); + xstrlcat(IObuff, shortmess(SHM_RO) ? _("[RO]") : _("[readonly]"), IOSIZE); c = true; } if (read_no_eol_lnum) { @@ -1716,18 +1716,18 @@ failed: c = true; } if (ff_error == EOL_DOS) { - STRCAT(IObuff, _("[CR missing]")); + xstrlcat(IObuff, _("[CR missing]"), IOSIZE); c = true; } if (split) { - STRCAT(IObuff, _("[long lines split]")); + xstrlcat(IObuff, _("[long lines split]"), IOSIZE); c = true; } if (notconverted) { - STRCAT(IObuff, _("[NOT converted]")); + xstrlcat(IObuff, _("[NOT converted]"), IOSIZE); c = true; } else if (converted) { - STRCAT(IObuff, _("[converted]")); + xstrlcat(IObuff, _("[converted]"), IOSIZE); c = true; } if (conv_error != 0) { @@ -1739,7 +1739,7 @@ failed: _("[ILLEGAL BYTE in line %" PRId64 "]"), (int64_t)illegal_byte); c = true; } else if (error) { - STRCAT(IObuff, _("[READ ERRORS]")); + xstrlcat(IObuff, _("[READ ERRORS]"), IOSIZE); c = true; } if (msg_add_fileformat(fileformat)) { @@ -2128,17 +2128,17 @@ bool msg_add_fileformat(int eol_type) { #ifndef USE_CRNL if (eol_type == EOL_DOS) { - STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[dos]") : _("[dos format]")); + xstrlcat(IObuff, shortmess(SHM_TEXT) ? _("[dos]") : _("[dos format]"), IOSIZE); return true; } #endif if (eol_type == EOL_MAC) { - STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[mac]") : _("[mac format]")); + xstrlcat(IObuff, shortmess(SHM_TEXT) ? _("[mac]") : _("[mac format]"), IOSIZE); return true; } #ifdef USE_CRNL if (eol_type == EOL_UNIX) { - STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[unix]") : _("[unix format]")); + xstrlcat(IObuff, shortmess(SHM_TEXT) ? _("[unix]") : _("[unix format]"), IOSIZE); return true; } #endif @@ -2170,8 +2170,7 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars) /// Append message for missing line separator to IObuff. void msg_add_eol(void) { - STRCAT(IObuff, - shortmess(SHM_LAST) ? _("[noeol]") : _("[Incomplete last line]")); + xstrlcat(IObuff, shortmess(SHM_LAST) ? _("[noeol]") : _("[Incomplete last line]"), IOSIZE); } bool time_differs(const FileInfo *file_info, long mtime, long mtime_ns) FUNC_ATTR_CONST diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index dae1365272..a9e7838980 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -20,6 +20,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/buffer_defs.h" #include "nvim/globals.h" +#include "nvim/lua/executor.h" #include "nvim/lua/treesitter.h" #include "nvim/macros.h" #include "nvim/map.h" @@ -43,6 +44,13 @@ typedef struct { int max_match_id; } TSLua_cursor; +typedef struct { + LuaRef cb; + lua_State *lstate; + bool lex; + bool parse; +} TSLuaLoggerOpts; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/treesitter.c.generated.h" #endif @@ -56,6 +64,8 @@ static struct luaL_Reg parser_meta[] = { { "included_ranges", parser_get_ranges }, { "set_timeout", parser_set_timeout }, { "timeout", parser_get_timeout }, + { "_set_logger", parser_set_logger }, + { "_logger", parser_get_logger }, { NULL, NULL } }; @@ -322,6 +332,12 @@ static int parser_gc(lua_State *L) return 0; } + TSLogger logger = ts_parser_logger(*p); + if (logger.log) { + TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)logger.payload; + xfree(opts); + } + ts_parser_delete(*p); return 0; } @@ -669,9 +685,82 @@ static int parser_get_timeout(lua_State *L) } lua_pushinteger(L, (long)ts_parser_timeout_micros(*p)); + return 1; +} + +static void logger_cb(void *payload, TSLogType logtype, const char *s) +{ + TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)payload; + if ((!opts->lex && logtype == TSLogTypeLex) + || (!opts->parse && logtype == TSLogTypeParse)) { + return; + } + + lua_State *lstate = opts->lstate; + + nlua_pushref(lstate, opts->cb); + lua_pushstring(lstate, logtype == TSLogTypeParse ? "parse" : "lex"); + lua_pushstring(lstate, s); + if (lua_pcall(lstate, 2, 0, 0)) { + luaL_error(lstate, "Error executing treesitter logger callback"); + } +} + +static int parser_set_logger(lua_State *L) +{ + TSParser **p = parser_check(L, 1); + if (!p) { + return 0; + } + + if (!lua_isboolean(L, 2)) { + return luaL_argerror(L, 2, "boolean expected"); + } + + if (!lua_isboolean(L, 3)) { + return luaL_argerror(L, 3, "boolean expected"); + } + + if (!lua_isfunction(L, 4)) { + return luaL_argerror(L, 4, "function expected"); + } + + TSLuaLoggerOpts *opts = xmalloc(sizeof(TSLuaLoggerOpts)); + + *opts = (TSLuaLoggerOpts){ + .lex = lua_toboolean(L, 2), + .parse = lua_toboolean(L, 3), + .cb = nlua_ref_global(L, 4), + .lstate = L + }; + + TSLogger logger = { + .payload = (void *)opts, + .log = logger_cb + }; + + ts_parser_set_logger(*p, logger); return 0; } +static int parser_get_logger(lua_State *L) +{ + TSParser **p = parser_check(L, 1); + if (!p) { + return 0; + } + + TSLogger logger = ts_parser_logger(*p); + if (logger.log) { + TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)logger.payload; + nlua_pushref(L, opts->cb); + } else { + lua_pushnil(L); + } + + return 1; +} + // Tree methods /// push tree interface on lua stack. diff --git a/src/nvim/main.c b/src/nvim/main.c index 659eccc6f0..4999d9dd2a 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -2222,3 +2222,17 @@ static void check_swap_exists_action(void) } handle_swap_exists(NULL); } + +#ifdef ENABLE_ASAN_UBSAN +const char *__ubsan_default_options(void); +const char *__ubsan_default_options(void) +{ + return "print_stacktrace=1"; +} + +const char *__asan_default_options(void); +const char *__asan_default_options(void) +{ + return "handle_abort=1,handle_sigill=1"; +} +#endif diff --git a/src/nvim/move.c b/src/nvim/move.c index 6a6f8149dd..ead2b3b0d3 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -1502,11 +1502,11 @@ void adjust_skipcol(void) } else { curwin->w_skipcol -= width1; } - redraw_later(curwin, UPD_NOT_VALID); scrolled = true; - validate_virtcol(); } if (scrolled) { + validate_virtcol(); + redraw_later(curwin, UPD_NOT_VALID); return; // don't scroll in the other direction now } long col = curwin->w_virtcol - curwin->w_skipcol + scrolloff_cols; diff --git a/src/nvim/option.c b/src/nvim/option.c index fc1fc87b62..327c5fc089 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1626,7 +1626,7 @@ int do_set(char *arg, int opt_flags) int i = (int)strlen(IObuff) + 2; if (i + (arg - startarg) < IOSIZE) { // append the argument with the error - STRCAT(IObuff, ": "); + xstrlcat(IObuff, ": ", IOSIZE); assert(arg >= startarg); memmove(IObuff + i, startarg, (size_t)(arg - startarg)); IObuff[i + (arg - startarg)] = NUL; diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c index 5235828f7a..8b62b9e895 100644 --- a/src/nvim/os/stdpaths.c +++ b/src/nvim/os/stdpaths.c @@ -130,7 +130,7 @@ char *get_xdg_home(const XDGVarType idx) xstrlcpy(IObuff, appname, appname_len + 1); #if defined(MSWIN) if (idx == kXDGDataHome || idx == kXDGStateHome) { - STRCAT(IObuff, "-data"); + xstrlcat(IObuff, "-data", IOSIZE); } #endif dir = concat_fnames_realloc(dir, IObuff, true); diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index f7b7723553..a9068fabc8 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -1544,7 +1544,7 @@ static inline char *add_dir(char *dest, const char *const dir, const size_t dir_ xstrlcpy(IObuff, appname, appname_len + 1); #if defined(MSWIN) if (type == kXDGDataHome || type == kXDGStateHome) { - STRCAT(IObuff, "-data"); + xstrlcat(IObuff, "-data", IOSIZE); appname_len += 5; } #endif diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 81bca67cf7..18331cc95d 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -732,7 +732,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose) num_matches, max_num_matches != MAXCOL ? _(" or more") : ""); if (ic) { - STRCAT(IObuff, _(" Using tag with different case!")); + xstrlcat(IObuff, _(" Using tag with different case!"), IOSIZE); } if ((num_matches > prev_num_matches || new_tag) && num_matches > 1) { diff --git a/src/nvim/testing.c b/src/nvim/testing.c index 5483a58525..25ec8e898a 100644 --- a/src/nvim/testing.c +++ b/src/nvim/testing.c @@ -418,11 +418,11 @@ static int assert_equalfile(typval_T *argvars) const int c2 = fgetc(fd2); if (c1 == EOF) { if (c2 != EOF) { - STRCPY(IObuff, "first file is shorter"); + xstrlcpy(IObuff, "first file is shorter", IOSIZE); } break; } else if (c2 == EOF) { - STRCPY(IObuff, "second file is shorter"); + xstrlcpy(IObuff, "second file is shorter", IOSIZE); break; } else { line1[lineidx] = (char)c1; diff --git a/src/nvim/textobject.c b/src/nvim/textobject.c index 428b14a68d..5036c10827 100644 --- a/src/nvim/textobject.c +++ b/src/nvim/textobject.c @@ -22,6 +22,7 @@ #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option_defs.h" #include "nvim/pos.h" @@ -414,6 +415,7 @@ int bck_word(long count, bool bigword, bool stop) finished: stop = false; } + adjust_skipcol(); return OK; } @@ -518,6 +520,7 @@ int bckend_word(long count, bool bigword, bool eol) } } } + adjust_skipcol(); return OK; } diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 35f46e512d..00a3922a5b 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2667,7 +2667,7 @@ void ex_undolist(exarg_T *eap) undo_fmt_time(IObuff + strlen(IObuff), IOSIZE - strlen(IObuff), uhp->uh_time); if (uhp->uh_save_nr > 0) { while (strlen(IObuff) < 33) { - STRCAT(IObuff, " "); + xstrlcat(IObuff, " ", IOSIZE); } vim_snprintf_add(IObuff, IOSIZE, " %3ld", uhp->uh_save_nr); } diff --git a/test/functional/legacy/display_spec.lua b/test/functional/legacy/display_spec.lua index feb2662100..482b88eae8 100644 --- a/test/functional/legacy/display_spec.lua +++ b/test/functional/legacy/display_spec.lua @@ -286,17 +286,56 @@ describe('display', function() local screen = Screen.new(75, 8) screen:attach() exec([[ - call setline(1, ['a', 'bbbbb '->repeat(100), 'c']) + call setline(1, ['a', 'b ' .. 'bbbbb'->repeat(150), 'c']) norm $j ]]) screen:expect([[ - <<<bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb | - bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbb| - bb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb | - bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbb| - bb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb | - bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbb| - bb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb^ | + <<<bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + b^b | + | + ]]) + -- FIXME: moving the cursor above the topline does not set w_skipcol + -- correctly with cpo+=n and zero scrolloff (curs_columns() extra == 1). + exec('set number cpo+=n scrolloff=0') + feed('$0') + screen:expect([[ + <<<b^bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + | + ]]) + -- Going to the start of the line with "b" did not set w_skipcol correctly with 'smoothscroll'. + exec('set smoothscroll') + feed('$b') + screen:expect([[ + 2 b ^bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + | + ]]) + -- Same for "ge". + feed('$ge') + screen:expect([[ + 2 ^b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| | ]]) end) diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 9afce0b3a0..14de07639b 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -483,10 +483,9 @@ end]] return list ]] - eq({ 'any-of?', 'contains?', 'eq?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list) + eq({ 'any-of?', 'contains?', 'eq?', 'has-ancestor?', 'has-parent?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list) end) - it('allows to set simple ranges', function() insert(test_text) @@ -528,6 +527,7 @@ end]] eq(range_tbl, { { { 0, 0, 0, 17, 1, 508 } } }) end) + it("allows to set complex ranges", function() insert(test_text) @@ -992,4 +992,58 @@ int x = INT_MAX; }, run_query()) end) + + it('handles ranges when source is a multiline string (#20419)', function() + local source = [==[ + vim.cmd[[ + set number + set cmdheight=2 + set lastsatus=2 + ]] + + set query = [[;; query + ((function_call + name: [ + (identifier) @_cdef_identifier + (_ _ (identifier) @_cdef_identifier) + ] + arguments: (arguments (string content: _ @injection.content))) + (#set! injection.language "c") + (#eq? @_cdef_identifier "cdef")) + ]] + ]==] + + local r = exec_lua([[ + local parser = vim.treesitter.get_string_parser(..., 'lua') + parser:parse() + local ranges = {} + parser:for_each_tree(function(tstree, tree) + ranges[tree:lang()] = { tstree:root():range(true) } + end) + return ranges + ]], source) + + eq({ + lua = { 0, 6, 6, 16, 4, 438 }, + query = { 6, 20, 113, 15, 6, 431 }, + vim = { 1, 0, 16, 4, 6, 89 } + }, r) + + -- The above ranges are provided directly from treesitter, however query directives may mutate + -- the ranges but only provide a Range4. Strip the byte entries from the ranges and make sure + -- add_bytes() produces the same result. + + local rb = exec_lua([[ + local r, source = ... + local add_bytes = require('vim.treesitter._range').add_bytes + for lang, range in pairs(r) do + r[lang] = {range[1], range[2], range[4], range[5]} + r[lang] = add_bytes(source, r[lang]) + end + return r + ]], r, source) + + eq(rb, r) + + end) end) diff --git a/test/old/testdir/test_display.vim b/test/old/testdir/test_display.vim index a2b40521d9..e007b6f741 100644 --- a/test/old/testdir/test_display.vim +++ b/test/old/testdir/test_display.vim @@ -518,14 +518,29 @@ func Test_display_cursor_long_line() CheckScreendump let lines =<< trim END - call setline(1, ['a', 'bbbbb '->repeat(100), 'c']) + call setline(1, ['a', 'b ' .. 'bbbbb'->repeat(150), 'c']) norm $j END call writefile(lines, 'XdispCursorLongline', 'D') let buf = RunVimInTerminal('-S XdispCursorLongline', #{rows: 8}) - call VerifyScreenDump(buf, 'Test_display_cursor_long_line', {}) + call VerifyScreenDump(buf, 'Test_display_cursor_long_line_1', {}) + + " FIXME: moving the cursor above the topline does not set w_skipcol + " correctly with cpo+=n and zero scrolloff (curs_columns() extra == 1). + call term_sendkeys(buf, ":set number cpo+=n scrolloff=0\<CR>") + call term_sendkeys(buf, '$0') + call VerifyScreenDump(buf, 'Test_display_cursor_long_line_2', {}) + + " Going to the start of the line with "b" did not set w_skipcol correctly + " with 'smoothscroll'. + call term_sendkeys(buf, ":set smoothscroll\<CR>") + call term_sendkeys(buf, '$b') + call VerifyScreenDump(buf, 'Test_display_cursor_long_line_3', {}) + " Same for "ge". + call term_sendkeys(buf, '$ge') + call VerifyScreenDump(buf, 'Test_display_cursor_long_line_4', {}) call StopVimInTerminal(buf) endfunc diff --git a/test/old/testdir/test_ex_mode.vim b/test/old/testdir/test_ex_mode.vim index b30dce8630..3332bc6ab9 100644 --- a/test/old/testdir/test_ex_mode.vim +++ b/test/old/testdir/test_ex_mode.vim @@ -218,9 +218,9 @@ func Test_Ex_echo_backslash() let bsl = '\\\\' let bsl2 = '\\\' call assert_fails('call feedkeys("Qecho " .. bsl .. "\nvisual\n", "xt")', - \ "E15: Invalid expression: \\\\") + \ 'E15: Invalid expression: "\\"') call assert_fails('call feedkeys("Qecho " .. bsl2 .. "\nm\nvisual\n", "xt")', - \ "E15: Invalid expression: \\\nm") + \ "E15: Invalid expression: \"\\\nm\"") endfunc func Test_ex_mode_errors() diff --git a/test/old/testdir/test_scroll_opt.vim b/test/old/testdir/test_scroll_opt.vim index 13ef4b76df..74c2de81aa 100644 --- a/test/old/testdir/test_scroll_opt.vim +++ b/test/old/testdir/test_scroll_opt.vim @@ -502,7 +502,7 @@ func Test_smoothscroll_cursor_position() " regardless of number and cpo-=n. setl number list listchars=precedes:< cpo-=n call s:check_col_calc(5, 1, 1) - exe "normal 2|\<C-E>" + exe "normal 3|\<C-E>h" call s:check_col_calc(6, 1, 18) norm h call s:check_col_calc(5, 2, 17) |