diff options
110 files changed, 2405 insertions, 1016 deletions
diff --git a/.luacheckrc b/.luacheckrc index 4d4d656b4b..ef17a8e80e 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -38,7 +38,8 @@ globals = { "vim.bo", "vim.wo", "vim.go", - "vim.env" + "vim.env", + "_", } exclude_files = { @@ -1,4 +1,14 @@ ifeq ($(OS),Windows_NT) + ifeq '$(findstring ;,$(PATH))' ';' + UNIX_LIKE := FALSE + else + UNIX_LIKE := TRUE + endif +else + UNIX_LIKE := TRUE +endif + +ifeq ($(UNIX_LIKE),FALSE) SHELL := powershell.exe .SHELLFLAGS := -NoProfile -NoLogo MKDIR := @$$null = new-item -itemtype directory -force diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index 51c107edf3..fc4ddef54b 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -46,8 +46,8 @@ TREESITTER_QUERY_URL https://github.com/tree-sitter-grammars/tree-sitter-query/a TREESITTER_QUERY_SHA256 e33907fd334350e32e49b3875c36bcf070defe490357632fac9398e6d4540a80 TREESITTER_MARKDOWN_URL https://github.com/tree-sitter-grammars/tree-sitter-markdown/archive/v0.3.2.tar.gz TREESITTER_MARKDOWN_SHA256 5dac48a6d971eb545aab665d59a18180d21963afc781bbf40f9077c06cb82ae5 -TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.25.1.tar.gz -TREESITTER_SHA256 99a2446075c2edf60e82755c48415d5f6e40f2d9aacb3423c6ca56809b70fe59 +TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.25.2.tar.gz +TREESITTER_SHA256 26791f69182192fef179cd58501c3226011158823557a86fe42682cb4a138523 WASMTIME_URL https://github.com/bytecodealliance/wasmtime/archive/v29.0.1.tar.gz WASMTIME_SHA256 b94b6c6fd6aebaf05d4c69c1b12b5dc217b0d42c1a95f435b33af63dddfa5304 diff --git a/runtime/compiler/svelte-check.vim b/runtime/compiler/svelte-check.vim new file mode 100644 index 0000000000..883aefdbc4 --- /dev/null +++ b/runtime/compiler/svelte-check.vim @@ -0,0 +1,19 @@ +" Vim compiler file +" Compiler: svelte-check +" Maintainer: @Konfekt +" Last Change: 2025 Feb 22 + +if exists("current_compiler") | finish | endif +let current_compiler = "svelte-check" + +CompilerSet makeprg=npx\ svelte-check\ --output\ machine +CompilerSet errorformat=%*\\d\ %t%*\\a\ \"%f\"\ %l:%c\ %m +CompilerSet errorformat+=%-G%.%# + +" " Fall-back for versions of svelte-check that don't support --output machine +" " before May 2020 https://github.com/sveltejs/language-tools/commit/9f7a90379d287a41621a5e78af5b010a8ab810c3 +" " which is before the first production release 1.1.31 of Svelte-Check +" CompilerSet makeprg=npx\ svelte-check +" CompilerSet errorformat=%E%f:%l:%c, +" CompilerSet errorformat+=%+ZError\:\ %m, +" CompilerSet errorformat+=%-G%.%# diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 4f76e7e058..ec3dfebbc0 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -2649,8 +2649,14 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts}) lines are placed below the buffer line containing the mark. • virt_lines_above: place virtual lines above instead. - • virt_lines_leftcol: Place extmarks in the leftmost column - of the window, bypassing sign and number columns. + • virt_lines_leftcol: Place virtual lines in the leftmost + column of the window, bypassing sign and number columns. + • virt_lines_overflow: controls how to handle virtual lines + wider than the window. Currently takes the one of the + following values: + • "trunc": truncate virtual lines on the right (default). + • "scroll": virtual lines can scroll horizontally with + 'nowrap', otherwise the same as "trunc". • ephemeral : for use with |nvim_set_decoration_provider()| callbacks. The mark will only be used for the current redraw cycle, and not be permantently stored in the diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 77d44c36a0..ae7fa8bcf9 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -2167,7 +2167,8 @@ extend({expr1}, {expr2} [, {expr3}]) *extend()* When {expr3} is omitted then "force" is assumed. {expr1} is changed when {expr2} is not empty. If necessary - make a copy of {expr1} first. + make a copy of {expr1} first or use |extendnew()| to return a + new List/Dictionary. {expr2} remains unchanged. When {expr1} is locked and {expr2} is not empty the operation fails. diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index 5e1e04ce56..bbc1d1de2c 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -373,7 +373,7 @@ EVENTS *diagnostic-events* *DiagnosticChanged* DiagnosticChanged After diagnostics have changed. When used from Lua, the new diagnostics are passed to the autocmd - callback in the "data" table. + callback in the "data" table. Triggered per buffer. Example: >lua diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt index cc520484b3..b3fe13bfbd 100644 --- a/runtime/doc/filetype.txt +++ b/runtime/doc/filetype.txt @@ -708,7 +708,7 @@ Options: You can also format quoted text with |gq|. Local mappings: -<LocalLeader>q or \\MailQuote +<LocalLeader>q or \MailQuote Quotes the text selected in Visual mode, or from the cursor position to the end of the file in Normal mode. This means "> " is inserted in each line. diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index 06f1a4e73d..8072b41466 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -1502,6 +1502,11 @@ both major engines implemented element, even if this is not in standards it will be suggested. All other elements are not placed in suggestion list. +LUA *ft-lua-omni* + +Lua |ftplugin| sets 'omnifunc' to |vim.lua_omnifunc()|. + + PHP *ft-php-omni* Completion of PHP code requires a tags file for completion of data from diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt index 0c654b8b30..5cb969a531 100644 --- a/runtime/doc/intro.txt +++ b/runtime/doc/intro.txt @@ -272,6 +272,8 @@ notation meaning equivalent decimal value(s) ~ <S-F1> - <S-F12> shift-function keys 1 to 12 *<S-F1>* <Help> help key <Undo> undo key +<Find> find key +<Select> select key <Insert> insert key <Home> home *home* <End> end *end* diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 93b5d3cdcc..37f4d43e2c 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -119,7 +119,7 @@ Results in the configuration: >lua root_markers = { '.clangd', 'compile_commands.json' }, -- From the clangd configuration in init.lua - -- Overrides the * configuration in init.lua + -- Overrides the clangd configuration in <rtp>/lsp/clangd.lua filetypes = { 'c' }, -- From the * configuration in init.lua diff --git a/runtime/doc/lua-bit.txt b/runtime/doc/lua-bit.txt new file mode 100644 index 0000000000..4c47010113 --- /dev/null +++ b/runtime/doc/lua-bit.txt @@ -0,0 +1,385 @@ +*lua-bit.txt* Nvim + *lua-bit* + + LUA BITOP REFERENCE MANUAL + + + Adapted from <https://bitop.luajit.org> + + +Lua BitOp is a C extension module for Lua 5.1/5.2 which adds bitwise +operations on numbers. + + Type |gO| to see the table of contents. + +============================================================================== +API FUNCTIONS *lua-bit-api* + +This list of API functions is not intended to replace a tutorial. If you are +not familiar with the terms used, you may want to study the Wikipedia article +on bitwise operations (https://en.wikipedia.org/wiki/Bitwise_operation) first. + +------------------------------------------------------------------------------ +Loading the BitOp module + *lua-bit-module* + +The suggested way to use the BitOp module is to add the following to the start +of every Lua file that needs one of its functions: >lua + local bit = require("bit") +< +This makes the dependency explicit, limits the scope to the current file and +provides faster access to the bit.* functions, too. It's good programming +practice not to rely on the global variable bit being set (assuming some other +part of your application has already loaded the module). The require function +ensures the module is only loaded once, in any case. + +------------------------------------------------------------------------------ +Defining Shortcuts + *lua-bit-shortcuts* + +It's a common (but not a required) practice to cache often used module +functions in locals. This serves as a shortcut to save some typing and also +speeds up resolving them (only relevant if called hundreds of thousands of +times). +>lua + local bnot = bit.bnot + local band, bor, bxor = bit.band, bit.bor, bit.bxor + local lshift, rshift, rol = bit.lshift, bit.rshift, bit.rol + -- etc... + + -- Example use of the shortcuts: + local function tr_i(a, b, c, d, x, s) + return rol(bxor(c, bor(b, bnot(d))) + a + x, s) + b + end +< + +Remember that `and`, `or` and `not` are reserved keywords in Lua. They cannot +be used for variable names or literal field names. That's why the +corresponding bitwise functions have been named `band`, `bor`, and `bnot` (and +`bxor` for consistency). + +While we are at it: a common pitfall is to use bit as the name of a local +temporary variable — well, don't! :-) + +------------------------------------------------------------------------------ +About the Examples + +The examples below show small Lua one-liners. Their expected output is shown +after `-->`. This is interpreted as a comment marker by Lua so you can cut & +paste the whole line to a Lua prompt and experiment with it. + +Note that all bit operations return signed 32 bit numbers (rationale). And +these print as signed decimal numbers by default. + +For clarity the examples assume the definition of a helper function +`printx()`. This prints its argument as an unsigned 32 bit hexadecimal number +on all platforms: +>lua + function printx(x) + print("0x"..bit.tohex(x)) + end +< +------------------------------------------------------------------------------ +Bit operations + *lua-bitop* + +y = bit.tobit(x) *bit.tobit()* + Normalizes a number to the numeric range for bit operations and returns + it. This function is usually not needed since all bit operations already + normalize all of their input arguments. Check the |luabit-semantics| for + details. + + Example: >lua + print(0xffffffff) --> 4294967295 (*) + print(bit.tobit(0xffffffff)) --> -1 + printx(bit.tobit(0xffffffff)) --> 0xffffffff + print(bit.tobit(0xffffffff + 1)) --> 0 + print(bit.tobit(2^40 + 1234)) --> 1234 +< + (*) See the treatment of |lua-bit-hex-literals| for an explanation why the + printed numbers in the first two lines differ (if your Lua installation + uses a double number type). + +y = bit.tohex(x [,n]) *bit.tohex()* + Converts its first argument to a hex string. The number of hex digits is + given by the absolute value of the optional second argument. Positive + numbers between 1 and 8 generate lowercase hex digits. Negative numbers + generate uppercase hex digits. Only the least-significant `4*|n|` bits are + used. The default is to generate 8 lowercase hex digits. + + Example: >lua + print(bit.tohex(1)) --> 00000001 + print(bit.tohex(-1)) --> ffffffff + print(bit.tohex(0xffffffff)) --> ffffffff + print(bit.tohex(-1, -8)) --> FFFFFFFF + print(bit.tohex(0x21, 4)) --> 0021 + print(bit.tohex(0x87654321, 4)) --> 4321 +< +y = bit.bnot(x) *bit.bnot()* + Returns the bitwise `not` of its argument. + + Example: >lua + print(bit.bnot(0)) --> -1 + printx(bit.bnot(0)) --> 0xffffffff + print(bit.bnot(-1)) --> 0 + print(bit.bnot(0xffffffff)) --> 0 + printx(bit.bnot(0x12345678)) --> 0xedcba987 +< +y = bit.bor(x1 [,x2...]) *bit.bor()* +y = bit.band(x1 [,x2...]) *bit.band()* +y = bit.bxor(x1 [,x2...]) *bit.bxor()* + Returns either the bitwise `or`, bitwise `and`, or bitwise `xor` of all of its + arguments. Note that more than two arguments are allowed. + + Example: >lua + print(bit.bor(1, 2, 4, 8)) --> 15 + printx(bit.band(0x12345678, 0xff)) --> 0x00000078 + printx(bit.bxor(0xa5a5f0f0, 0xaa55ff00)) --> 0x0ff00ff0 +< +y = bit.lshift(x, n) *bit.lshift()* +y = bit.rshift(x, n) *bit.rshift()* +y = bit.arshift(x, n) *bit.arshift()* + Returns either the bitwise `logical left-shift`, bitwise `logical` + `right-shift`, or bitwise `arithmetic right-shift` of its first argument + by the number of bits given by the second argument. + + Logical shifts treat the first argument as an unsigned number and shift in + 0-bits. Arithmetic right-shift treats the most-significant bit as a sign + bit and replicates it. Only the lower 5 bits of the shift count are used + (reduces to the range [0..31]). + + Example: >lua + print(bit.lshift(1, 0)) --> 1 + print(bit.lshift(1, 8)) --> 256 + print(bit.lshift(1, 40)) --> 256 + print(bit.rshift(256, 8)) --> 1 + print(bit.rshift(-256, 8)) --> 16777215 + print(bit.arshift(256, 8)) --> 1 + print(bit.arshift(-256, 8)) --> -1 + printx(bit.lshift(0x87654321, 12)) --> 0x54321000 + printx(bit.rshift(0x87654321, 12)) --> 0x00087654 + printx(bit.arshift(0x87654321, 12)) --> 0xfff87654 +< +y = bit.rol(x, n) *bit.rol()* +y = bit.ror(x, n) *bit.ror()* + Returns either the bitwise `left rotation`, or bitwise `right rotation` of its + first argument by the number of bits given by the second argument. Bits + shifted out on one side are shifted back in on the other side. + + Only the lower 5 bits of the rotate count are used (reduces to the range + [0..31]). + + Example: >lua + printx(bit.rol(0x12345678, 12)) --> 0x45678123 + printx(bit.ror(0x12345678, 12)) --> 0x67812345 +< +y = bit.bswap(x) + Swaps the bytes of its argument and returns it. This can be used to + convert little-endian 32 bit numbers to big-endian 32 bit numbers or vice + versa. + + Example: >lua + printx(bit.bswap(0x12345678)) --> 0x78563412 + printx(bit.bswap(0x78563412)) --> 0x12345678 +< +------------------------------------------------------------------------------ +Example Program + +This is an implementation of the (naïve) Sieve of Eratosthenes algorithm. It +counts the number of primes up to some maximum number. + +A Lua table is used to hold a bit-vector. Every array index has 32 bits of the +vector. Bitwise operations are used to access and modify them. Note that the +shift counts don't need to be masked since this is already done by the BitOp +shift and rotate functions. +>lua + local bit = require("bit") + local band, bxor = bit.band, bit.bxor + local rshift, rol = bit.rshift, bit.rol + + local m = tonumber(arg and arg[1]) or 100000 + if m < 2 then m = 2 end + local count = 0 + local p = {} + + for i=0,(m+31)/32 do p[i] = -1 end + + for i=2,m do + if band(rshift(p[rshift(i, 5)], i), 1) ~= 0 then + count = count + 1 + for j=i+i,m,i do + local jx = rshift(j, 5) + p[jx] = band(p[jx], rol(-2, j)) + end + end + end + + io.write(string.format("Found %d primes up to %d\n", count, m)) +< +Lua BitOp is quite fast. This program runs in less than 90 milliseconds on a 3 +GHz CPU with a standard Lua installation, but performs more than a million +calls to bitwise functions. If you're looking for even more speed, check out +|lua-luajit|. + +------------------------------------------------------------------------------ +Caveats *lua-bit-caveats* + +Signed Results ~ + +Returning signed numbers from bitwise operations may be surprising to +programmers coming from other programming languages which have both signed and +unsigned types. But as long as you treat the results of bitwise operations +uniformly everywhere, this shouldn't cause any problems. + +Preferably format results with `bit.tohex` if you want a reliable unsigned +string representation. Avoid the `"%x"` or `"%u"` formats for `string.format`. They +fail on some architectures for negative numbers and can return more than 8 hex +digits on others. + +You may also want to avoid the default number to string coercion, since this +is a signed conversion. The coercion is used for string concatenation and all +standard library functions which accept string arguments (such as `print()` or +`io.write()`). + +Conditionals ~ + +If you're transcribing some code from C/C++, watch out for bit operations in +conditionals. In C/C++ any non-zero value is implicitly considered as `true`. +E.g. this C code: >c + if (x & 3) ... +< +must not be turned into this Lua code: >lua + if band(x, 3) then ... -- wrong! +< +In Lua all objects except `nil` and `false` are considered `true`. This +includes all numbers. An explicit comparison against zero is required in this +case: >lua + if band(x, 3) ~= 0 then ... -- correct! + +Comparing Against Hex Literals ~ + +Comparing the results of bitwise operations (signed numbers) against hex +literals (unsigned numbers) needs some additional care. The following +conditional expression may or may not work right, depending on the platform +you run it on: >lua + bit.bor(x, 1) == 0xffffffff +< +E.g. it's never true on a Lua installation with the default number type. Some +simple solutions: + + Never use hex literals larger than 0x7fffffff in comparisons: >lua + bit.bor(x, 1) == -1 +< + Or convert them with bit.tobit() before comparing: >lua + bit.bor(x, 1) == bit.tobit(0xffffffff) +< + Or use a generic workaround with bit.bxor(): >lua + bit.bxor(bit.bor(x, 1), 0xffffffff) == 0 +< + Or use a case-specific workaround: >lua + bit.rshift(x, 1) == 0x7fffffff +< +============================================================================== +OPERATIONAL SEMANTICS AND RATIONALE *lua-bit-semantics* + + +Input and Output Ranges ~ + *lua-bit-io-ranges* + +Bitwise operations cannot sensibly be applied to FP numbers (or their +underlying bit patterns). They must be converted to integers before operating +on them and then back to FP numbers. + +It's desirable to define semantics that work the same across all platforms. +This dictates that all operations are based on the common denominator of 32 +bit integers. The `float` type provides only 24 bits of precision. This makes it +unsuitable for use in bitwise operations. Lua BitOp refuses to compile against +a Lua installation with this number type. + +Bit operations only deal with the underlying bit patterns and generally ignore +signedness (except for arithmetic right-shift). They are commonly displayed +and treated like unsigned numbers, though. + +But the Lua number type must be signed and may be limited to 32 bits. Defining +the result type as an unsigned number would not be cross-platform safe. All +bit operations are thus defined to return results in the range of signed 32 +bit numbers (converted to the Lua number type). + + *lua-bit-hex-literals* +Hexadecimal literals are treated as unsigned numbers by the Lua parser before +converting them to the Lua number type. This means they can be out of the +range of signed 32 bit integers if the Lua number type has a greater range. +E.g. 0xffffffff has a value of 4294967295 in the default installation, but may +be -1 on embedded systems. It's highly desirable that hex literals are treated +uniformly across systems when used in bitwise operations. All bit operations +accept arguments in the signed or the unsigned 32 bit range (and more, see +below). Numbers with the same underlying bit pattern are treated the same by +all operations. + + +Modular Arithmetic ~ + *lua-bit-modular-arith* + +Arithmetic operations on n-bit integers are usually based on the rules of +modular arithmetic modulo 2^n. Numbers wrap around when the mathematical result +of operations is outside their defined range. This simplifies hardware +implementations and some algorithms actually require this behavior (like many +cryptographic functions). + +E.g. for 32 bit integers the following holds: `0xffffffff + 1 = 0` + +Arithmetic modulo 2^32 is trivially available if the Lua number type is a 32 +bit integer. Otherwise normalization steps must be inserted. Modular +arithmetic should work the same across all platforms as far as possible: + +- For the default number type of double, arguments can be in the range of + ±2^51 and still be safely normalized across all platforms by taking their + least-significant 32 bits. The limit is derived from the way doubles are + converted to integers. +- The function bit.tobit can be used to explicitly normalize numbers to + implement modular addition or subtraction. E.g. >lua + bit.tobit(0xffffffff + 1) + returns 0 on all platforms. +- The limit on the argument range implies that modular multiplication is + usually restricted to multiplying already normalized numbers with small + constants. FP numbers are limited to 53 bits of precision, anyway. E.g. + (2^30+1)^2 does not return an odd number when computed with doubles. + +BTW: The `tr_i` function shown here |lua-bit-shortcuts| is one of the +non-linear functions of the (flawed) MD5 cryptographic hash and relies on +modular arithmetic for correct operation. The result is fed back to other +bitwise operations (not shown) and does not need to be normalized until the +last step. + + +Restricted and undefined behaviors ~ + *lua-bit-restrictions* + +The following rules are intended to give a precise and useful definition (for +the programmer), yet give the implementation (interpreter and compiler) the +maximum flexibility and the freedom to apply advanced optimizations. It's +strongly advised not to rely on undefined or implementation-defined behavior. + +- All kinds of floating-point numbers are acceptable to the bitwise + operations. None of them cause an error, but some may invoke undefined + behavior: + - -0 is treated the same as +0 on input and is never returned as a result. + - Passing ±Inf, NaN or numbers outside the range of ±2^51 as input yields + an undefined result. + - Non-integral numbers may be rounded or truncated in an + implementation-defined way. This means the result could differ between + different BitOp versions, different Lua VMs, on different platforms or + even between interpreted vs. compiled code (as in |LuaJIT|). Avoid + passing fractional numbers to bitwise functions. Use `math.floor()` or + `math.ceil()` to get defined behavior. +- Lua provides auto-coercion of string arguments to numbers by default. This + behavior is deprecated for bitwise operations. + +============================================================================== +COPYRIGHT + +Lua BitOp is Copyright (C) 2008-2012 Mike Pall. +Lua BitOp is free software, released under the MIT license. + +============================================================================== + vim:tw=78:ts=4:sw=4:sts=4:et:ft=help:norl: diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 0eca3286d0..ef2125841d 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -46,10 +46,9 @@ before using them: >lua -- code for plain lua 5.1 end < - *lua-bit* One exception is the LuaJIT `bit` extension, which is always available: when built with PUC Lua, Nvim includes a fallback implementation which provides -`require("bit")`. +`require("bit")`. See |lua-bit|. *lua-profile* If Nvim is built with LuaJIT, Lua code can be profiled via >lua diff --git a/runtime/doc/luvref.txt b/runtime/doc/luvref.txt index 3cbdb53e01..648060e1d5 100644 --- a/runtime/doc/luvref.txt +++ b/runtime/doc/luvref.txt @@ -226,7 +226,7 @@ ERROR HANDLING *luv-error-handling* In libuv, errors are represented by negative numbered constants. While these constants are made available in the `uv.errno` table, they are not returned by -luv funtions and the libuv functions used to handle them are not exposed. +luv functions and the libuv functions used to handle them are not exposed. Instead, if an internal error is encountered, the failing luv function will return to the caller an assertable `nil, err, name` tuple: diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index c98084adb6..e8ec6d1820 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -175,6 +175,7 @@ TREESITTER the tree before returning. Scripts must call |LanguageTree:parse()| explicitly. >lua local p = vim.treesitter.get_parser(0, 'c') p:parse() +• |vim.treesitter.get_parser()| expects its buffer to be loaded. < TUI @@ -201,9 +202,12 @@ API • |nvim_echo()| `err` field to print error messages and `chunks` accepts highlight group IDs. • |nvim_open_win()| `relative` field can be set to "laststatus" and "tabline". -• |nvim_buf_set_extmark()| `hl_group` field can be an array of layered groups +• |nvim_buf_set_extmark()| new field `virt_lines_overflow` accepts value `scroll` to + enable horizontal scrolling for virtual lines with 'nowrap'. + right aligned text that truncates before covering up buffer text. +• |nvim_buf_set_extmark()| `hl_group` field can be an array of layered groups. • |vim.hl.range()| now has a optional `timeout` field which allows for a timed highlight -• |nvim_buf_set_extmark()| virt_text_pos accepts `eol_right_align` to +• |nvim_buf_set_extmark()| `virt_text_pos` field accepts value `eol_right_align` to allow for right aligned text that truncates before covering up buffer text. DEFAULTS @@ -235,6 +239,9 @@ DEFAULTS • |[b|, |]b|, |[B|, |]B| navigate through the |buffer-list| • |[<Space>|, |]<Space>| add an empty line above and below the cursor +• Options: + • Lua |ftplugin| sets |'omnifunc'| to `"v:lua.vim.lua_omnifunc"`. + • Snippet: • `<Tab>` in Insert and Select mode maps to `vim.snippet.jump({ direction = 1 })` when a snippet is active and jumpable forwards. @@ -261,6 +268,9 @@ EDITOR to a literal "~" directory. • |hl-ComplMatchIns| shows matched text of the currently inserted completion. • |hl-PmenuMatch| and |hl-PmenuMatchSel| show matched text in completion popup. +• |gO| now works in `help`, `checkhealth`, and `markdown` buffers. +• Jump between sections in `help` and `checkhealth` buffers with `[[` and + `]]`. EVENTS @@ -302,6 +312,7 @@ LUA • Command-line completions for: `vim.g`, `vim.t`, `vim.w`, `vim.b`, `vim.v`, `vim.o`, `vim.wo`, `vim.bo`, `vim.opt`, `vim.opt_local`, `vim.opt_global`, and `vim.fn`. +• Documentation for |lua-bit|. • |vim.fs.rm()| can delete files and directories. • |vim.validate()| now has a new signature which uses less tables, is more performant and easier to read. @@ -335,6 +346,11 @@ PERFORMANCE • Treesitter highlighting is now asynchronous. To force synchronous parsing, use `vim.g._ts_force_sync_parsing = true`. • Treesitter folding is now calculated asynchronously. +• |LanguageTree:parse()| now only runs the injection query on the provided + range (as long as the language does not have a combined injection), + significantly improving |treesitter-highlight| performance. +• Treesitter injection query iteration is now asynchronous, making edits in + large buffers with combined injections much quicker. PLUGINS diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index b04f13add5..344bd37ddd 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -713,7 +713,7 @@ TSNode:extra() *TSNode:extra()* (`boolean`) TSNode:field({name}) *TSNode:field()* - Returns a table of the nodes corresponding to the {name} field. + Returns a list of all the node's children that have the given field name. Parameters: ~ • {name} (`string`) diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index d8a0e3d0d7..a3e09a9ea5 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -791,7 +791,8 @@ must handle. Name indicating the message kind: "" (empty) Unknown (consider a |feature-request|) "bufwrite" |:write| message - "confirm" |confirm()| or |:confirm| dialog + "confirm" Message preceding a prompt (|:confirm|, + |confirm()|, |inputlist()|, |z=,|, …) "emsg" Error (|errors|, internal error, |:throw|, …) "echo" |:echo| message "echomsg" |:echomsg| message diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index eae341da49..1e1f6c4db7 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -46,6 +46,7 @@ Defaults *defaults* *nvim-defaults* - 'compatible' is always disabled - 'complete' excludes "i" - 'define' defaults to "". The C ftplugin sets it to "^\\s*#\\s*define" +- 'diffopt' defaults to "internal,filler,closeoff,linematch:40" - 'directory' defaults to ~/.local/state/nvim/swap// (|xdg|), auto-created - 'display' defaults to "lastline" - 'encoding' is UTF-8 (cf. 'fileencoding' for file-content encoding) diff --git a/runtime/ftplugin/checkhealth.lua b/runtime/ftplugin/checkhealth.lua new file mode 100644 index 0000000000..d0f8cec074 --- /dev/null +++ b/runtime/ftplugin/checkhealth.lua @@ -0,0 +1,14 @@ +vim.keymap.set('n', 'gO', function() + require('vim.treesitter._headings').show_toc() +end, { buffer = 0, silent = true, desc = 'Show table of contents for current buffer' }) + +vim.keymap.set('n', ']]', function() + require('vim.treesitter._headings').jump({ count = 1, level = 1 }) +end, { buffer = 0, silent = false, desc = 'Jump to next section' }) +vim.keymap.set('n', '[[', function() + require('vim.treesitter._headings').jump({ count = -1, level = 1 }) +end, { buffer = 0, silent = false, desc = 'Jump to previous section' }) + +vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') + .. '\n sil! exe "nunmap <buffer> gO"' + .. '\n sil! exe "nunmap <buffer> ]]" | sil! exe "nunmap <buffer> [["' diff --git a/runtime/ftplugin/dnsmasq.vim b/runtime/ftplugin/dnsmasq.vim new file mode 100644 index 0000000000..bf93bf9300 --- /dev/null +++ b/runtime/ftplugin/dnsmasq.vim @@ -0,0 +1,11 @@ +" Vim filetype plugin +" Language: dnsmasq +" Maintainer: dringsim <dringsim@qq.com> +" Last Change: 2025-02-18 + +if exists('b:did_ftplugin') + finish +endif + +" Behaves mostly just like cfg +runtime! ftplugin/cfg.vim diff --git a/runtime/ftplugin/dockerfile.vim b/runtime/ftplugin/dockerfile.vim index e45bf4c1d8..f9268fe89b 100644 --- a/runtime/ftplugin/dockerfile.vim +++ b/runtime/ftplugin/dockerfile.vim @@ -1,7 +1,7 @@ " Vim filetype plugin " Language: Dockerfile " Maintainer: Honza Pokorny <http://honza.ca> -" Last Change: 2024 Dec 20 +" Last Change: 2025 Feb 21 " Only do this when not done yet for this buffer if exists("b:did_ftplugin") @@ -11,6 +11,7 @@ endif " Don't load another plugin for this buffer let b:did_ftplugin = 1 +setlocal comments=:# setlocal commentstring=#\ %s -let b:undo_ftplugin = "setl commentstring<" +let b:undo_ftplugin = "setl comments< commentstring<" diff --git a/runtime/ftplugin/dosini.vim b/runtime/ftplugin/dosini.vim index 6a53dfd096..3470bd3b3b 100644 --- a/runtime/ftplugin/dosini.vim +++ b/runtime/ftplugin/dosini.vim @@ -1,7 +1,8 @@ " Vim filetype plugin file " Language: Configuration File (ini file) for MS-DOS/MS Windows +" Maintainer: This runtime file is looking for a new maintainer. " Previous Maintainer: Nikolai Weibull <now@bitwi.se> -" Latest Revision: 2008-07-09 +" Latest Revision: 2025 Feb 20 if exists("b:did_ftplugin") finish @@ -13,7 +14,7 @@ set cpo&vim let b:undo_ftplugin = "setl com< cms< fo<" -setlocal comments=:; commentstring=;\ %s formatoptions-=t formatoptions+=croql +setlocal comments=:;,:# commentstring=;\ %s formatoptions-=t formatoptions+=croql let &cpo = s:cpo_save unlet s:cpo_save diff --git a/runtime/ftplugin/help.lua b/runtime/ftplugin/help.lua index a6169a1d9d..e19571454d 100644 --- a/runtime/ftplugin/help.lua +++ b/runtime/ftplugin/help.lua @@ -1,15 +1,43 @@ -- use treesitter over syntax (for highlighted code blocks) vim.treesitter.start() +--- Apply current colorscheme to lists of default highlight groups +--- +--- Note: {patterns} is assumed to be sorted by occurrence in the file. +--- @param patterns {start:string,stop:string,match:string}[] +local function colorize_hl_groups(patterns) + local ns = vim.api.nvim_create_namespace('nvim.vimhelp') + vim.api.nvim_buf_clear_namespace(0, ns, 0, -1) + + local save_cursor = vim.fn.getcurpos() + + for _, pat in pairs(patterns) do + local start_lnum = vim.fn.search(pat.start, 'c') + local end_lnum = vim.fn.search(pat.stop) + if start_lnum == 0 or end_lnum == 0 then + break + end + + for lnum = start_lnum, end_lnum do + local word = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, true)[1]:match(pat.match) + if vim.fn.hlexists(word) ~= 0 then + vim.api.nvim_buf_set_extmark(0, ns, lnum - 1, 0, { end_col = #word, hl_group = word }) + end + end + end + + vim.fn.setpos('.', save_cursor) +end + -- Add custom highlights for list in `:h highlight-groups`. local bufname = vim.fs.normalize(vim.api.nvim_buf_get_name(0)) if vim.endswith(bufname, '/doc/syntax.txt') then - require('vim.vimhelp').highlight_groups({ + colorize_hl_groups({ { start = [[\*group-name\*]], stop = '^======', match = '^(%w+)\t' }, { start = [[\*highlight-groups\*]], stop = '^======', match = '^(%w+)\t' }, }) elseif vim.endswith(bufname, '/doc/treesitter.txt') then - require('vim.vimhelp').highlight_groups({ + colorize_hl_groups({ { start = [[\*treesitter-highlight-groups\*]], stop = [[\*treesitter-highlight-spell\*]], @@ -17,24 +45,31 @@ elseif vim.endswith(bufname, '/doc/treesitter.txt') then }, }) elseif vim.endswith(bufname, '/doc/diagnostic.txt') then - require('vim.vimhelp').highlight_groups({ + colorize_hl_groups({ { start = [[\*diagnostic-highlights\*]], stop = '^======', match = '^(%w+)' }, }) elseif vim.endswith(bufname, '/doc/lsp.txt') then - require('vim.vimhelp').highlight_groups({ + colorize_hl_groups({ { start = [[\*lsp-highlight\*]], stop = '^------', match = '^(%w+)' }, { start = [[\*lsp-semantic-highlight\*]], stop = '^======', match = '^@[%w%p]+' }, }) end vim.keymap.set('n', 'gO', function() - require('vim.vimhelp').show_toc() -end, { buffer = 0, silent = true }) + require('vim.treesitter._headings').show_toc() +end, { buffer = 0, silent = true, desc = 'Show table of contents for current buffer' }) + +vim.keymap.set('n', ']]', function() + require('vim.treesitter._headings').jump({ count = 1 }) +end, { buffer = 0, silent = false, desc = 'Jump to next section' }) +vim.keymap.set('n', '[[', function() + require('vim.treesitter._headings').jump({ count = -1 }) +end, { buffer = 0, silent = false, desc = 'Jump to previous section' }) -- Add "runnables" for Lua/Vimscript code examples. ---@type table<integer, { lang: string, code: string }> local code_blocks = {} -local tree = vim.treesitter.get_parser():parse()[1] +local parser = assert(vim.treesitter.get_parser(0, 'vimdoc', { error = false })) local query = vim.treesitter.query.parse( 'vimdoc', [[ @@ -46,10 +81,11 @@ local query = vim.treesitter.query.parse( (#set! @code lang @_lang)) ]] ) +local root = parser:parse()[1]:root() local run_message_ns = vim.api.nvim_create_namespace('nvim.vimdoc.run_message') vim.api.nvim_buf_clear_namespace(0, run_message_ns, 0, -1) -for _, match, metadata in query:iter_matches(tree:root(), 0, 0, -1) do +for _, match, metadata in query:iter_matches(root, 0, 0, -1) do for id, nodes in pairs(match) do local name = query.captures[id] local node = nodes[1] @@ -82,5 +118,6 @@ vim.keymap.set('n', 'g==', function() end, { buffer = true }) vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') - .. '\n exe "nunmap <buffer> gO" | exe "nunmap <buffer> g=="' + .. '\n sil! exe "nunmap <buffer> gO" | sil! exe "nunmap <buffer> g=="' + .. '\n sil! exe "nunmap <buffer> ]]" | sil! exe "nunmap <buffer> [["' vim.b.undo_ftplugin = vim.b.undo_ftplugin .. ' | call v:lua.vim.treesitter.stop()' diff --git a/runtime/ftplugin/lua.lua b/runtime/ftplugin/lua.lua index 75deb6b190..e2a7631b29 100644 --- a/runtime/ftplugin/lua.lua +++ b/runtime/ftplugin/lua.lua @@ -1,4 +1,7 @@ -- use treesitter over syntax vim.treesitter.start() -vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') .. '\n call v:lua.vim.treesitter.stop()' +vim.bo.omnifunc = 'v:lua.vim.lua_omnifunc' + +vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') + .. '\n call v:lua.vim.treesitter.stop() \n setl omnifunc<' diff --git a/runtime/ftplugin/mail.vim b/runtime/ftplugin/mail.vim index 3cef84f528..de88f4d1e8 100644 --- a/runtime/ftplugin/mail.vim +++ b/runtime/ftplugin/mail.vim @@ -1,7 +1,7 @@ " Vim filetype plugin file " Language: Mail " Maintainer: The Vim Project <https://github.com/vim/vim> -" Last Change: 2023 Aug 10 +" Last Change: 2025 Feb 20 " Former Maintainer: Bram Moolenaar <Bram@vim.org> " Only do this when not done yet for this buffer @@ -10,7 +10,7 @@ if exists("b:did_ftplugin") endif let b:did_ftplugin = 1 -let b:undo_ftplugin = "setl modeline< tw< fo< comments<" +let b:undo_ftplugin = "setl modeline< tw< fo< comments< commentstring<" " Don't use modelines in e-mail messages, avoid trojan horses and nasty " "jokes" (e.g., setting 'textwidth' to 5). @@ -24,6 +24,9 @@ endif " Set 'formatoptions' to break text lines and keep the comment leader ">". setlocal fo+=tcql +" Set commentstring to quoting sign ">" so comment shortcuts can be used to +" edit quoted parts of mail +setlocal commentstring=>\ %s " Add n:> to 'comments, in case it was removed elsewhere setlocal comments+=n:> diff --git a/runtime/ftplugin/markdown.lua b/runtime/ftplugin/markdown.lua new file mode 100644 index 0000000000..d9958706c8 --- /dev/null +++ b/runtime/ftplugin/markdown.lua @@ -0,0 +1,14 @@ +vim.keymap.set('n', 'gO', function() + require('vim.treesitter._headings').show_toc() +end, { buffer = 0, silent = true, desc = 'Show table of contents for current buffer' }) + +vim.keymap.set('n', ']]', function() + require('vim.treesitter._headings').jump({ count = 1 }) +end, { buffer = 0, silent = false, desc = 'Jump to next section' }) +vim.keymap.set('n', '[[', function() + require('vim.treesitter._headings').jump({ count = -1 }) +end, { buffer = 0, silent = false, desc = 'Jump to previous section' }) + +vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') + .. '\n sil! exe "nunmap <buffer> gO"' + .. '\n sil! exe "nunmap <buffer> ]]" | sil! exe "nunmap <buffer> [["' diff --git a/runtime/ftplugin/sieve.vim b/runtime/ftplugin/sieve.vim index 3092b5d2d3..8161fe99ac 100644 --- a/runtime/ftplugin/sieve.vim +++ b/runtime/ftplugin/sieve.vim @@ -1,20 +1,19 @@ " Vim filetype plugin file " Language: Sieve filtering language input file +" Maintainer: This runtime file is looking for a new maintainer. " Previous Maintainer: Nikolai Weibull <now@bitwi.se> -" Latest Revision: 2008-07-09 +" Latest Revision: 2025 Feb 20 if exists("b:did_ftplugin") finish endif let b:did_ftplugin = 1 -let s:cpo_save = &cpo -set cpo&vim - -let b:undo_ftplugin = "setl com< cms< fo<" +let b:undo_ftplugin = "setl com< cms< fo< ff<" setlocal comments=s1:/*,mb:*,ex:*/,:# commentstring=#\ %s setlocal formatoptions-=t formatoptions+=croql -let &cpo = s:cpo_save -unlet s:cpo_save +" https://datatracker.ietf.org/doc/html/rfc5228#section-2.2 says +" "newlines (CRLF, never just CR or LF)" +setlocal fileformat=dos diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 6c8ef0ad9f..c2e4e76dd6 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -24,6 +24,14 @@ do vim.api.nvim_create_user_command('EditQuery', function(cmd) vim.treesitter.query.edit(cmd.fargs[1]) end, { desc = 'Edit treesitter query', nargs = '?' }) + + vim.api.nvim_create_user_command('Open', function(cmd) + vim.ui.open(cmd.fargs[1]) + end, { + desc = 'Open file with system default handler. See :help vim.ui.open()', + nargs = 1, + complete = 'file', + }) end --- Default mappings diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index a77ea9bb91..94168bea5d 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -56,7 +56,7 @@ vim._extra = { inspect_pos = true, } ---- @private +--- @nodoc vim.log = { --- @enum vim.log.levels levels = { diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 3d10729d23..a6ffb43146 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -638,10 +638,14 @@ function vim.api.nvim_buf_line_count(buffer) end --- placed below the buffer line containing the mark. --- --- - virt_lines_above: place virtual lines above instead. ---- - virt_lines_leftcol: Place extmarks in the leftmost +--- - virt_lines_leftcol: Place virtual lines in the leftmost --- column of the window, bypassing --- sign and number columns. ---- +--- - virt_lines_overflow: controls how to handle virtual lines wider +--- than the window. Currently takes the one of the following values: +--- - "trunc": truncate virtual lines on the right (default). +--- - "scroll": virtual lines can scroll horizontally with 'nowrap', +--- otherwise the same as "trunc". --- - ephemeral : for use with `nvim_set_decoration_provider()` --- callbacks. The mark will only be used for the current --- redraw cycle, and not be permantently stored in the diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index 4d0665872b..a66e373851 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -258,6 +258,7 @@ error('Cannot require a meta file') --- @field virt_lines? any[] --- @field virt_lines_above? boolean --- @field virt_lines_leftcol? boolean +--- @field virt_lines_overflow? string --- @field strict? boolean --- @field sign_text? string --- @field sign_hl_group? integer|string diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index f4c395ce39..4970a3023b 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -1928,7 +1928,8 @@ function vim.fn.expandcmd(string, options) end --- When {expr3} is omitted then "force" is assumed. --- --- {expr1} is changed when {expr2} is not empty. If necessary ---- make a copy of {expr1} first. +--- make a copy of {expr1} first or use |extendnew()| to return a +--- new List/Dictionary. --- {expr2} remains unchanged. --- When {expr1} is locked and {expr2} is not empty the operation --- fails. diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua index 973ad87ee8..e98b95f837 100644 --- a/runtime/lua/vim/_options.lua +++ b/runtime/lua/vim/_options.lua @@ -922,11 +922,11 @@ function Option:prepend(value) end -- luacheck: no unused ---@diagnostic disable-next-line:unused-local used for gen_vimdoc function Option:remove(value) end -- luacheck: no unused ----@private +--- @nodoc vim.opt = create_option_accessor() ----@private +--- @nodoc vim.opt_local = create_option_accessor('local') ----@private +--- @nodoc vim.opt_global = create_option_accessor('global') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 6974b6508d..66ee45587a 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1802,6 +1802,7 @@ local filename = { Vagrantfile = 'ruby', ['smb.conf'] = 'samba', ['.lips_repl_history'] = 'scheme', + ['.guile'] = 'scheme', screenrc = 'screen', ['.screenrc'] = 'screen', ['/etc/sensors3.conf'] = 'sensors', @@ -2150,11 +2151,6 @@ local pattern = { ['/usr/.*/gnupg/options%.skel$'] = 'gpg', ['/usr/share/upstart/.*%.conf$'] = 'upstart', ['/usr/share/upstart/.*%.override$'] = 'upstart', - ['/usr/share/X11/xkb/compat/'] = detect_xkb, - ['/usr/share/X11/xkb/geometry/'] = detect_xkb, - ['/usr/share/X11/xkb/keycodes/'] = detect_xkb, - ['/usr/share/X11/xkb/symbols/'] = detect_xkb, - ['/usr/share/X11/xkb/types/'] = detect_xkb, }, ['/var/'] = { ['/var/backups/group%.bak$'] = 'group', @@ -2329,6 +2325,13 @@ local pattern = { ['^Neomuttrc'] = detect_neomuttrc, ['%.neomuttdebug'] = 'neomuttlog', }, + ['/%.?xkb/'] = { + ['/%.?xkb/compat/'] = detect_xkb, + ['/%.?xkb/geometry/'] = detect_xkb, + ['/%.?xkb/keycodes/'] = detect_xkb, + ['/%.?xkb/symbols/'] = detect_xkb, + ['/%.?xkb/types/'] = detect_xkb, + }, ['^%.'] = { ['^%.cshrc'] = detect.csh, ['^%.login'] = detect.csh, diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua index d99b1ffd0d..1466dcf438 100644 --- a/runtime/lua/vim/lsp/completion.lua +++ b/runtime/lua/vim/lsp/completion.lua @@ -132,7 +132,7 @@ end --- @return string local function get_completion_word(item, prefix, match) if item.insertTextFormat == protocol.InsertTextFormat.Snippet then - if item.textEdit then + if item.textEdit or (item.insertText and item.insertText ~= '') then -- Use label instead of text if text has different starting characters. -- label is used as abbr (=displayed), but word is used for filtering -- This is required for things like postfix completion. @@ -154,8 +154,6 @@ local function get_completion_word(item, prefix, match) else return word end - elseif item.insertText and item.insertText ~= '' then - return parse_snippet(item.insertText) else return item.label end diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 9e84e27205..905b9822ba 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1417,6 +1417,11 @@ function M._make_floating_popup_size(contents, opts) -- make sure borders are always inside the screen width = math.min(width, screen_width - border_width) + -- Make sure that the width is large enough to fit the title. + if opts.title then + width = math.max(width, vim.fn.strdisplaywidth(opts.title)) + end + if wrap_at then wrap_at = math.min(wrap_at, width) end diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 10638e10d8..44372415fd 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -34,8 +34,6 @@ M.minimum_language_version = vim._ts_get_minimum_language_version() function M._create_parser(bufnr, lang, opts) bufnr = vim._resolve_bufnr(bufnr) - vim.fn.bufload(bufnr) - local self = LanguageTree.new(bufnr, lang, opts) local function bytes_cb(_, ...) @@ -102,6 +100,9 @@ function M.get_parser(bufnr, lang, opts) return nil, err_msg end elseif parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then + if not api.nvim_buf_is_loaded(bufnr) then + error(('Buffer %s must be loaded to create parser'):format(bufnr)) + end local parser = vim.F.npcall(M._create_parser, bufnr, lang, opts) if not parser then local err_msg = @@ -415,6 +416,14 @@ end ---@param lang string? Language of the parser (default: from buffer filetype) function M.start(bufnr, lang) bufnr = vim._resolve_bufnr(bufnr) + -- Ensure buffer is loaded. `:edit` over `bufload()` to show swapfile prompt. + if not api.nvim_buf_is_loaded(bufnr) then + if api.nvim_buf_get_name(bufnr) ~= '' then + pcall(api.nvim_buf_call, bufnr, vim.cmd.edit) + else + vim.fn.bufload(bufnr) + end + end local parser = assert(M.get_parser(bufnr, lang, { error = false })) M.highlighter.new(parser) end diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 38318347a7..1064004320 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -75,7 +75,15 @@ local function compute_folds_levels(bufnr, info, srow, erow, callback) erow = erow or api.nvim_buf_line_count(bufnr) local parser = info.parser - if not parser then + if + not parser + -- Parsing an empty buffer results in problems with the parsing state, + -- resulting in both a broken highlighter and foldexpr. + or api.nvim_buf_line_count(bufnr) == 1 + and api.nvim_buf_call(bufnr, function() + return vim.fn.line2byte(1) <= 0 + end) + then return end @@ -380,7 +388,7 @@ function M.foldexpr(lnum) if not foldinfos[bufnr] then foldinfos[bufnr] = FoldInfo.new(bufnr) - api.nvim_create_autocmd({ 'BufUnload', 'VimEnter' }, { + api.nvim_create_autocmd({ 'BufUnload', 'VimEnter', 'FileType' }, { buffer = bufnr, once = true, callback = function() diff --git a/runtime/lua/vim/treesitter/_headings.lua b/runtime/lua/vim/treesitter/_headings.lua new file mode 100644 index 0000000000..4e8833c93a --- /dev/null +++ b/runtime/lua/vim/treesitter/_headings.lua @@ -0,0 +1,144 @@ +local ts = vim.treesitter +local api = vim.api + +--- Treesitter-based navigation functions for headings +local M = {} + +-- TODO(clason): use runtimepath queries (for other languages) +local heading_queries = { + vimdoc = [[ + (h1 (heading) @h1) + (h2 (heading) @h2) + (h3 (heading) @h3) + (column_heading (heading) @h4) + ]], + markdown = [[ + (setext_heading + heading_content: (_) @h1 + (setext_h1_underline)) + (setext_heading + heading_content: (_) @h2 + (setext_h2_underline)) + (atx_heading + (atx_h1_marker) + heading_content: (_) @h1) + (atx_heading + (atx_h2_marker) + heading_content: (_) @h2) + (atx_heading + (atx_h3_marker) + heading_content: (_) @h3) + (atx_heading + (atx_h4_marker) + heading_content: (_) @h4) + (atx_heading + (atx_h5_marker) + heading_content: (_) @h5) + (atx_heading + (atx_h6_marker) + heading_content: (_) @h6) + ]], +} + +local function hash_tick(bufnr) + return tostring(vim.b[bufnr].changedtick) +end + +---@class TS.Heading +---@field bufnr integer +---@field lnum integer +---@field text string +---@field level integer + +--- Extract headings from buffer +--- @param bufnr integer buffer to extract headings from +--- @return TS.Heading[] +local get_headings = vim.func._memoize(hash_tick, function(bufnr) + local lang = ts.language.get_lang(vim.bo[bufnr].filetype) + if not lang then + return {} + end + local parser = assert(ts.get_parser(bufnr, lang, { error = false })) + local query = ts.query.parse(lang, heading_queries[lang]) + local root = parser:parse()[1]:root() + local headings = {} + for id, node, _, _ in query:iter_captures(root, bufnr) do + local text = ts.get_node_text(node, bufnr) + local row, col = node:start() + --- why can't you just be normal?! + local skip ---@type boolean|integer + if lang == 'vimdoc' then + -- only column_headings at col 1 are headings, otherwise it's code examples + skip = (id == 4 and col > 0) + -- ignore tabular material + or (id == 4 and (text:find('\t') or text:find(' '))) + -- ignore tag-only headings + or (node:child_count() == 1 and node:child(0):type() == 'tag') + end + if not skip then + table.insert(headings, { + bufnr = bufnr, + lnum = row + 1, + text = text, + level = id, + }) + end + end + return headings +end) + +--- Show a table of contents for the help buffer in a loclist +function M.show_toc() + local bufnr = api.nvim_get_current_buf() + local headings = get_headings(bufnr) + if #headings == 0 then + return + end + -- add indentation for nicer list formatting + for _, heading in pairs(headings) do + if heading.level > 2 then + heading.text = ' ' .. heading.text + end + if heading.level > 4 then + heading.text = ' ' .. heading.text + end + end + vim.fn.setloclist(0, headings, ' ') + vim.fn.setloclist(0, {}, 'a', { title = 'Help TOC' }) + vim.cmd.lopen() +end + +--- Jump to section +--- @param opts table jump options +--- - count integer direction to jump (>0 forward, <0 backward) +--- - level integer only consider headings up to level +--- todo(clason): support count +function M.jump(opts) + local bufnr = api.nvim_get_current_buf() + local headings = get_headings(bufnr) + if #headings == 0 then + return + end + + local winid = api.nvim_get_current_win() + local curpos = vim.fn.getcurpos(winid)[2] --[[@as integer]] + local maxlevel = opts.level or 6 + + if opts.count > 0 then + for _, heading in ipairs(headings) do + if heading.lnum > curpos and heading.level <= maxlevel then + api.nvim_win_set_cursor(winid, { heading.lnum, 0 }) + return + end + end + elseif opts.count < 0 then + for i = #headings, 1, -1 do + if headings[i].lnum < curpos and headings[i].level <= maxlevel then + api.nvim_win_set_cursor(winid, { headings[i].lnum, 0 }) + return + end + end + end +end + +return M diff --git a/runtime/lua/vim/treesitter/_meta/tsnode.lua b/runtime/lua/vim/treesitter/_meta/tsnode.lua index 552905c3f0..2f9d7f214a 100644 --- a/runtime/lua/vim/treesitter/_meta/tsnode.lua +++ b/runtime/lua/vim/treesitter/_meta/tsnode.lua @@ -43,7 +43,7 @@ function TSNode:prev_named_sibling() end --- @return fun(): TSNode, string function TSNode:iter_children() end ---- Returns a table of the nodes corresponding to the {name} field. +--- Returns a list of all the node's children that have the given field name. --- @param name string --- @return TSNode[] function TSNode:field(name) end diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 6dd47811bd..475a1f0aa5 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -232,7 +232,12 @@ end ---@return vim.treesitter.highlighter.Query function TSHighlighter:get_query(lang) if not self._queries[lang] then - self._queries[lang] = TSHighlighterQuery.new(lang) + local success, result = pcall(TSHighlighterQuery.new, lang) + if not success then + self:destroy() + error(result) + end + self._queries[lang] = result end return self._queries[lang] diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index 16d19bfc5a..38d309a102 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -5,6 +5,7 @@ local M = {} ---@type table<string,string> local ft_to_lang = { help = 'vimdoc', + checkhealth = 'vimdoc', } --- Returns the filetypes for which a parser named {lang} is used. diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index d8db489d54..f2e745ec65 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -46,6 +46,9 @@ local Range = require('vim.treesitter._range') local default_parse_timeout_ms = 3 +---@type Range2 +local entire_document_range = { 0, math.huge } + ---@alias TSCallbackName ---| 'changedtree' ---| 'bytes' @@ -77,7 +80,7 @@ local TSCallbackNames = { ---@field package _callbacks_rec table<TSCallbackName,function[]> Callback handlers (recursive) ---@field private _children table<string,vim.treesitter.LanguageTree> Injected languages ---@field private _injection_query vim.treesitter.Query Queries defining injected languages ----@field private _injections_processed boolean +---@field private _processed_injection_range Range? Range for which injections have been processed ---@field private _opts table Options ---@field private _parser TSParser Parser for language ---Table of regions for which the tree is currently running an async parse @@ -95,6 +98,7 @@ local TSCallbackNames = { ---@field private _trees table<integer, TSTree> Reference to parsed tree (one for each language). ---Each key is the index of region, which is synced with _regions and _valid. ---@field private _valid_regions table<integer,true> Set of valid region IDs. +---@field private _num_valid_regions integer Number of valid regions ---@field private _is_entirely_valid boolean Whether the entire tree (excluding children) is valid. ---@field private _logger? fun(logtype: string, msg: string) ---@field private _logfile? file* @@ -136,8 +140,9 @@ function LanguageTree.new(source, lang, opts) _opts = opts, _injection_query = injections[lang] and query.parse(lang, injections[lang]) or query.get(lang, 'injections'), - _injections_processed = false, + _processed_injection_range = nil, _valid_regions = {}, + _num_valid_regions = 0, _num_regions = 1, _is_entirely_valid = false, _parser = vim._create_ts_parser(lang), @@ -246,6 +251,7 @@ end ---@param reload boolean|nil function LanguageTree:invalidate(reload) self._valid_regions = {} + self._num_valid_regions = 0 self._is_entirely_valid = false self._parser:reset() @@ -331,7 +337,10 @@ function LanguageTree:is_valid(exclude_children, range) end if not exclude_children then - if not self._injections_processed then + if + not self._processed_injection_range + or not Range.contains(self._processed_injection_range, range or entire_document_range) + then return false end @@ -363,7 +372,6 @@ end --- @return Range6[] changes --- @return integer no_regions_parsed --- @return number total_parse_time ---- @return boolean finished whether async parsing still needs time function LanguageTree:_parse_regions(range, thread_state) local changes = {} local no_regions_parsed = 0 @@ -388,12 +396,14 @@ function LanguageTree:_parse_regions(range, thread_state) if tree then break end - coroutine.yield(changes, no_regions_parsed, total_parse_time, false) + coroutine.yield(self._trees, false) parse_time, tree, tree_changes = tcall(self._parser.parse, self._parser, self._trees[i], self._source, true) end + self:_subtract_time(thread_state, parse_time) + self:_do_callback('changedtree', tree_changes, tree) self._trees[i] = tree vim.list_extend(changes, tree_changes) @@ -401,24 +411,22 @@ function LanguageTree:_parse_regions(range, thread_state) total_parse_time = total_parse_time + parse_time no_regions_parsed = no_regions_parsed + 1 self._valid_regions[i] = true + self._num_valid_regions = self._num_valid_regions + 1 - -- _valid_regions can have holes, but that is okay because this equality is only true when it - -- has no holes (meaning all regions are valid) - if #self._valid_regions == self._num_regions then + if self._num_valid_regions == self._num_regions then self._is_entirely_valid = true end end end - return changes, no_regions_parsed, total_parse_time, true + return changes, no_regions_parsed, total_parse_time end --- @private ---- @return number -function LanguageTree:_add_injections() +--- @param injections_by_lang table<string, Range6[][]> +function LanguageTree:_add_injections(injections_by_lang) local seen_langs = {} ---@type table<string,boolean> - local query_time, injections_by_lang = tcall(self._get_injections, self) for lang, injection_regions in pairs(injections_by_lang) do local has_lang = pcall(language.add, lang) @@ -442,8 +450,6 @@ function LanguageTree:_add_injections() self:remove_child(lang) end end - - return query_time end --- @param range boolean|Range? @@ -567,6 +573,15 @@ function LanguageTree:parse(range, on_parse) return trees end +---@param thread_state ParserThreadState +---@param time integer +function LanguageTree:_subtract_time(thread_state, time) + thread_state.timeout = thread_state.timeout and math.max(thread_state.timeout - time, 0) + if thread_state.timeout == 0 then + coroutine.yield(self._trees, false) + end +end + --- @private --- @param range boolean|Range|nil --- @param thread_state ParserThreadState @@ -587,28 +602,27 @@ function LanguageTree:_parse(range, thread_state) -- At least 1 region is invalid if not self:is_valid(true, type(range) == 'table' and range or nil) then - ---@type fun(self: vim.treesitter.LanguageTree, range: boolean|Range?, thread_state: ParserThreadState): Range6[], integer, number, boolean - local parse_regions = coroutine.wrap(self._parse_regions) - while true do - local is_finished - changes, no_regions_parsed, total_parse_time, is_finished = - parse_regions(self, range, thread_state) - thread_state.timeout = thread_state.timeout - and math.max(thread_state.timeout - total_parse_time, 0) - if is_finished then - break - end - coroutine.yield(self._trees, false) - end + changes, no_regions_parsed, total_parse_time = self:_parse_regions(range, thread_state) + -- Need to run injections when we parsed something if no_regions_parsed > 0 then - self._injections_processed = false + self._processed_injection_range = nil end end - if not self._injections_processed and range then - query_time = self:_add_injections() - self._injections_processed = true + if + range + and not ( + self._processed_injection_range + and Range.contains( + self._processed_injection_range, + range ~= true and range or entire_document_range + ) + ) + then + local injections_by_lang = self:_get_injections(range, thread_state) + local time = tcall(self._add_injections, self, injections_by_lang) + self:_subtract_time(thread_state, time) end self:_log({ @@ -620,21 +634,7 @@ function LanguageTree:_parse(range, thread_state) }) for _, child in pairs(self._children) do - if thread_state.timeout == 0 then - coroutine.yield(self._trees, false) - end - - ---@type fun(): table<integer, TSTree>, boolean - local parse = coroutine.wrap(child._parse) - - while true do - local ctime, _, child_finished = tcall(parse, child, range, thread_state) - if child_finished then - thread_state.timeout = thread_state.timeout and math.max(thread_state.timeout - ctime, 0) - break - end - coroutine.yield(self._trees, child_finished) - end + child:_parse(range, thread_state) end return self._trees, true @@ -745,6 +745,7 @@ function LanguageTree:_iter_regions(fn) -- just by checking the length of _valid_regions. self._valid_regions[i] = fn(i, region) and true or nil if not self._valid_regions[i] then + self._num_valid_regions = self._num_valid_regions - 1 self:_log(function() return 'invalidating region', i, region_tostr(region) end) @@ -983,18 +984,29 @@ end --- TODO: Allow for an offset predicate to tailor the injection range --- instead of using the entire nodes range. --- @private +--- @param range Range|true +--- @param thread_state ParserThreadState --- @return table<string, Range6[][]> -function LanguageTree:_get_injections() +function LanguageTree:_get_injections(range, thread_state) if not self._injection_query or #self._injection_query.captures == 0 then + self._processed_injection_range = entire_document_range return {} end ---@type table<integer,vim.treesitter.languagetree.Injection> local injections = {} + local start = vim.uv.hrtime() + + local full_scan = range == true or self._injection_query.has_combined_injections for index, tree in pairs(self._trees) do local root_node = tree:root() - local start_line, _, end_line, _ = root_node:range() + local start_line, end_line ---@type integer, integer + if full_scan then + start_line, _, end_line = root_node:range() + else + start_line, _, end_line = Range.unpack4(range --[[@as Range]]) + end for pattern, match, metadata in self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1) @@ -1005,6 +1017,11 @@ function LanguageTree:_get_injections() else self:_log('match from injection query failed for pattern', pattern) end + + -- Check the current function duration against the timeout, if it exists. + local current_time = vim.uv.hrtime() + self:_subtract_time(thread_state, (current_time - start) / 1000000) + start = current_time end end @@ -1031,6 +1048,12 @@ function LanguageTree:_get_injections() end end + if full_scan then + self._processed_injection_range = entire_document_range + else + self._processed_injection_range = range --[[@as Range]] + end + return result end diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 10fb82e533..d26aa8e604 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -30,9 +30,11 @@ end --- Splits the query patterns into predicates and directives. ---@param patterns table<integer, (integer|string)[][]> ---@return table<integer, vim.treesitter.query.ProcessedPattern> +---@return boolean local function process_patterns(patterns) ---@type table<integer, vim.treesitter.query.ProcessedPattern> local processed_patterns = {} + local has_combined = false for k, pattern_list in pairs(patterns) do ---@type vim.treesitter.query.ProcessedPredicate[] @@ -47,6 +49,9 @@ local function process_patterns(patterns) if is_directive(pred_name) then table.insert(directives, pattern) + if vim.deep_equal(pattern, { 'set!', 'injection.combined' }) then + has_combined = true + end else local should_match = true if pred_name:match('^not%-') then @@ -60,7 +65,7 @@ local function process_patterns(patterns) processed_patterns[k] = { predicates = predicates, directives = directives } end - return processed_patterns + return processed_patterns, has_combined end ---@nodoc @@ -71,6 +76,7 @@ end ---@field captures string[] list of (unique) capture names defined in query ---@field info vim.treesitter.QueryInfo query context (e.g. captures, predicates, directives) ---@field query TSQuery userdata query object +---@field has_combined_injections boolean whether the query contains combined injections ---@field private _processed_patterns table<integer, vim.treesitter.query.ProcessedPattern> local Query = {} Query.__index = Query @@ -90,7 +96,7 @@ function Query.new(lang, ts_query) patterns = query_info.patterns, } self.captures = self.info.captures - self._processed_patterns = process_patterns(self.info.patterns) + self._processed_patterns, self.has_combined_injections = process_patterns(self.info.patterns) return self end diff --git a/runtime/lua/vim/vimhelp.lua b/runtime/lua/vim/vimhelp.lua deleted file mode 100644 index a494d311b1..0000000000 --- a/runtime/lua/vim/vimhelp.lua +++ /dev/null @@ -1,71 +0,0 @@ --- Extra functionality for displaying Vim help. - -local M = {} - ---- Apply current colorscheme to lists of default highlight groups ---- ---- Note: {patterns} is assumed to be sorted by occurrence in the file. ---- @param patterns {start:string,stop:string,match:string}[] -function M.highlight_groups(patterns) - local ns = vim.api.nvim_create_namespace('nvim.vimhelp') - vim.api.nvim_buf_clear_namespace(0, ns, 0, -1) - - local save_cursor = vim.fn.getcurpos() - - for _, pat in pairs(patterns) do - local start_lnum = vim.fn.search(pat.start, 'c') - local end_lnum = vim.fn.search(pat.stop) - if start_lnum == 0 or end_lnum == 0 then - break - end - - for lnum = start_lnum, end_lnum do - local word = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, true)[1]:match(pat.match) - if vim.fn.hlexists(word) ~= 0 then - vim.api.nvim_buf_set_extmark(0, ns, lnum - 1, 0, { end_col = #word, hl_group = word }) - end - end - end - - vim.fn.setpos('.', save_cursor) -end - ---- Show a table of contents for the help buffer in a loclist -function M.show_toc() - local bufnr = vim.api.nvim_get_current_buf() - local parser = assert(vim.treesitter.get_parser(bufnr, 'vimdoc', { error = false })) - local query = vim.treesitter.query.parse( - parser:lang(), - [[ - (h1 (heading) @h1) - (h2 (heading) @h2) - (h3 (heading) @h3) - (column_heading (heading) @h4) - ]] - ) - local root = parser:parse()[1]:root() - local headings = {} - for id, node, _, _ in query:iter_captures(root, bufnr) do - local text = vim.treesitter.get_node_text(node, bufnr) - local capture = query.captures[id] - local row, col = node:start() - -- only column_headings at col 1 are headings, otherwise it's code examples - local is_code = (capture == 'h4' and col > 0) - -- ignore tabular material - local is_table = (capture == 'h4' and (text:find('\t') or text:find(' '))) - -- ignore tag-only headings - local is_tag = node:child_count() == 1 and node:child(0):type() == 'tag' - if not (is_code or is_table or is_tag) then - table.insert(headings, { - bufnr = bufnr, - lnum = row + 1, - text = (capture == 'h3' or capture == 'h4') and ' ' .. text or text, - }) - end - end - vim.fn.setloclist(0, headings, ' ') - vim.fn.setloclist(0, {}, 'a', { title = 'Help TOC' }) - vim.cmd.lopen() -end - -return M diff --git a/runtime/pack/dist/opt/netrw/doc/netrw.txt b/runtime/pack/dist/opt/netrw/doc/netrw.txt index 9fc7b42bb7..b1f9d3a927 100644 --- a/runtime/pack/dist/opt/netrw/doc/netrw.txt +++ b/runtime/pack/dist/opt/netrw/doc/netrw.txt @@ -58,7 +58,6 @@ Copyright: Copyright (C) 2017 Charles E Campbell *netrw-copyright* Changing local-only File Permission.................|netrw-gp| Changing To A Predecessor Directory.................|netrw-u| Changing To A Successor Directory...................|netrw-U| - Customizing Browsing With A Special Handler.........|netrw-x| Deleting Bookmarks..................................|netrw-mB| Deleting Files Or Directories.......................|netrw-D| Directory Exploring Commands........................|netrw-explore| @@ -401,9 +400,6 @@ settings are described below, in |netrw-browser-options|, and in *g:netrw_menu* =0 disable netrw's menu =1 (default) netrw's menu enabled - *g:netrw_nogx* if this variable exists, then the "gx" map will not - be available (see |netrw-gx|) - *g:netrw_uid* (ftp) user-id, retained on a per-vim-session basis *s:netrw_passwd* (ftp) password, retained on a per-vim-session basis @@ -1108,7 +1104,7 @@ QUICK REFERENCE: MAPS *netrw-browse-maps* {{{2 U Change to subsequently-visited directory |netrw-U| v Enter the file/directory under the cursor in a new |netrw-v| browser window. A vertical split is used. - x View file with an associated program |netrw-x| + x View file with an associated program X Execute filename under cursor via |system()| |netrw-X| % Open a new file in netrw's current directory |netrw-%| @@ -1461,106 +1457,6 @@ With either form of the command, netrw will first ask for confirmation that the removal is in fact what you want to do. If netrw doesn't have permission to remove a file, it will issue an error message. -CUSTOMIZING BROWSING WITH A SPECIAL HANDLER *netrw-x* *netrw-handler* {{{2 - -Certain files, such as html, gif, jpeg, (word/office) doc, etc, files, are -best seen with a special handler (ie. a tool provided with your computer's -operating system). Netrw allows one to invoke such special handlers by: - - * hitting gx with the cursor atop the file path or alternatively x - in a netrw buffer; the former can be disabled by defining the - |g:netrw_nogx| variable - * when in command line, typing :Open <path>, see |:Open| below. - -One may also use visual mode (see |visual-start|) to select the text that the -special handler will use. Normally gx checks for a close-by URL or file name -to pick up the text under the cursor; one may change what |expand()| uses via the -|g:netrw_gx| variable (options include "<cword>", "<cWORD>"). Note that -expand("<cfile>") depends on the |'isfname'| setting. Alternatively, one may -select the text to be used by gx by making a visual selection (see -|visual-block|) and then pressing gx. - -The selection function can be adapted for each filetype by adding a function -`Netrw_get_URL_<filetype>`, where <filetype> is given by the 'filetype'. -The function should return the URL or file name to be used by gx, and will -fall back to the default behavior if it returns an empty string. -For example, special handlers for links Markdown and HTML are - -" make gx work on concealed links regardless of exact cursor position: > - - function Netrw_get_URL_markdown() - " markdown URL such as [link text](http://ya.ru 'yandex search') - try - let save_view = winsaveview() - if searchpair('\[.\{-}\](', '', ')\zs', 'cbW', '', line('.')) > 0 - return matchstr(getline('.')[col('.')-1:], - \ '\[.\{-}\](\zs' .. g:netrw_regex_url .. '\ze\(\s\+.\{-}\)\?)') - endif - return '' - finally - call winrestview(save_view) - endtry - endfunction - - function Netrw_get_URL_html() - " HTML URL such as <a href='http://www.python.org'>Python is here</a> - " <a href="http://www.python.org"/> - try - let save_view = winsaveview() - if searchpair('<a\s\+href=', '', '\%(</a>\|/>\)\zs', 'cbW', '', line('.')) > 0 - return matchstr(getline('.')[col('.') - 1 : ], - \ 'href=["'.."'"..']\?\zs\S\{-}\ze["'.."'"..']\?/\?>') - endif - return '' - finally - call winrestview(save_view) - endtry - endfunction -< -Other than a file path, the text under the cursor may be a URL. Netrw uses -by default the following regular expression to determine if the text under the -cursor is a URL: -> - :let g:netrw_regex_url = '\%(\%(http\|ftp\|irc\)s\?\|file\)://\S\{-}' -< -Associated setting variables: - |g:netrw_gx| control how gx picks up the text under the cursor - |g:netrw_nogx| prevent gx map while editing - |g:netrw_suppress_gx_mesg| controls gx's suppression of browser messages - -OPENING FILES AND LAUNCHING APPS *netrw-gx* *:Open* *:Launch* {{{2 - -Netrw determines which special handler by the following method: - - * if |g:netrw_browsex_viewer| exists, then it will be used to attempt to - view files. - If the viewer you wish to use does not support handling of a remote URL - directory, set |g:netrw_browsex_support_remote| to 0. - * otherwise: - - * for Windows : explorer.exe is used - * for Mac OS X : open is used. - * for Linux : xdg-open is used. - -To open a path (or URL) <path> by the appropriate handler, type > - - :Open <path> -< -No escaping, neither for the shell nor for Vim's command-line, is needed. - -To launch a specific application <app> <args>, often <args> being <path> > - - :Launch <app> <args>. - -Since <args> can be arbitrarily complex, in particular contain many file -paths, the escaping is left to the user. - -If you disabled the netrw plugin by setting g:loaded_netrwPlugin (see -|netrw-noload|), then you can use > - - :call netrw#Launch('<app> <args>') - :call netrw#Open('<path>') -< *netrw-curdir* DELETING BOOKMARKS *netrw-mB* {{{2 @@ -2570,14 +2466,6 @@ your browsing preferences. (see also: |netrw-settings|) |netrw-C| |netrw-cr| |netrw-ctrl-r| - *g:netrw_browsex_viewer* specify user's preference for a viewer: > - "kfmclient exec" - "gnome-open" -< - *g:netrw_browsex_support_remote* - specify if the specified viewer supports a - remote URL. (see |netrw-handler|). - *g:netrw_chgperm* Unix/Linux: "chmod PERM FILENAME" Windows: "cacls FILENAME /e /p PERM" Used to change access permission for a file. @@ -2600,12 +2488,11 @@ your browsing preferences. (see also: |netrw-settings|) *g:Netrw_corehandler* Allows one to specify something additional to do when handling <core> files via netrw's - browser's "x" command (see |netrw-x|). If - present, g:Netrw_corehandler specifies - either one or more function references - (see |Funcref|). (the capital g:Netrw... - is required its holding a function reference) - + browser's "x" command. If present, + g:Netrw_corehandler specifies either one or + more function references (see |Funcref|). + (the capital g:Netrw... is required its + holding a function reference) *g:netrw_ctags* ="ctags" The default external program used to create @@ -2754,11 +2641,6 @@ your browsing preferences. (see also: |netrw-settings|) These characters in directory names are escaped before applying glob() - *g:netrw_gx* ="<cfile>" - This option controls how gx (|netrw-gx|) picks - up the text under the cursor. See |expand()| - for possibilities. - *g:netrw_hide* Controlled by the "a" map (see |netrw-a|) =0 : show all =1 : show not-hidden files diff --git a/runtime/pack/dist/opt/netrw/plugin/netrwPlugin.vim b/runtime/pack/dist/opt/netrw/plugin/netrwPlugin.vim index 8d10c00153..388a7f2ba3 100644 --- a/runtime/pack/dist/opt/netrw/plugin/netrwPlugin.vim +++ b/runtime/pack/dist/opt/netrw/plugin/netrwPlugin.vim @@ -20,12 +20,6 @@ let g:loaded_netrwPlugin = "v175" let s:keepcpo = &cpo set cpo&vim -" Commands Launch/URL: {{{ - -command -complete=shellcmd -nargs=1 Launch call netrw#Launch(trim(<q-args>)) -command -complete=file -nargs=1 Open call netrw#Open(trim(<q-args>)) - -" }}} " Local Browsing Autocmds: {{{ augroup FileExplorer @@ -85,21 +79,6 @@ command! -bang NetrwClean call netrw#Clean(<bang>0) " }}} " Maps: {{{ -if !exists("g:netrw_nogx") - if maparg('gx','n') == "" - if !hasmapto('<Plug>NetrwBrowseX') - nmap <unique> gx <Plug>NetrwBrowseX - endif - nno <silent> <Plug>NetrwBrowseX :call netrw#BrowseX(netrw#GX(),netrw#CheckIfRemote(netrw#GX()))<cr> - endif - if maparg('gx','x') == "" - if !hasmapto('<Plug>NetrwBrowseXVis') - xmap <unique> gx <Plug>NetrwBrowseXVis - endif - xno <silent> <Plug>NetrwBrowseXVis :<c-u>call netrw#BrowseXVis()<cr> - endif -endif - if exists("g:netrw_usetab") && g:netrw_usetab if maparg('<c-tab>','n') == "" nmap <unique> <c-tab> <Plug>NetrwShrink diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 3d62f883b2..9dfccf0bc7 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -192,7 +192,6 @@ syn match vimNumber '\<0o\=\o\+' skipwhite nextgroup=vimGlobal,vimSubst1,@vimC syn match vimNumber '\<0x\x\+' skipwhite nextgroup=vimGlobal,vimSubst1,@vimComment,vimSubscript syn match vimNumber '\<0z\>' skipwhite nextgroup=vimGlobal,vimSubst1,@vimComment syn match vimNumber '\<0z\%(\x\x\)\+\%(\.\%(\x\x\)\+\)*' skipwhite nextgroup=vimGlobal,vimSubst1,@vimComment,vimSubscript -syn match vimNumber '\%(^\|\A\)\zs#\x\{6}' skipwhite nextgroup=vimGlobal,vimSubst1,@vimComment syn case match " All vimCommands are contained by vimIsCommand. {{{2 @@ -353,7 +352,7 @@ syn match vim9LambdaOperatorComment contained "#.*" skipwhite skipempty nextgrou syn cluster vimFuncList contains=vimFuncBang,vimFunctionError,vimFuncKey,vimFuncScope,vimFuncSID,Tag syn cluster vimDefList contains=vimFuncBang,vimFunctionError,vimDefKey,vimFuncScope,vimFuncSID,Tag -syn cluster vimFuncBodyCommon contains=@vimCmdList,vimCmplxRepeat,vimContinue,vimCtrlChar,vimDef,vimFBVar,vimFunc,vimFunction,vimLetHereDoc,vimNotation,vimNotFunc,vimNumber,vimOper,vimOperParen,vimRegister,vimSpecFile,vimString,vimSubst,vimFuncFold,vimDefFold +syn cluster vimFuncBodyCommon contains=@vimCmdList,vimCmplxRepeat,vimContinue,vimCtrlChar,vimDef,vimFBVar,vimFunc,vimFunction,vimLetHereDoc,vimNotFunc,vimNumber,vimOper,vimOperParen,vimRegister,vimSpecFile,vimString,vimSubst,vimFuncFold,vimDefFold syn cluster vimFuncBodyList contains=@vimFuncBodyCommon,vimComment,vimLineComment,vimInsert,vimConst,vimLet,vimSearch syn cluster vimDefBodyList contains=@vimFuncBodyCommon,vim9Comment,vim9LineComment,vim9Block,vim9Const,vim9Final,vim9Var,vim9Null,vim9Boolean,vim9For,vim9LhsVariable,vim9LhsVariableList,vim9LhsRegister,vim9Search,@vimSpecialVar @@ -625,7 +624,10 @@ syn match vimCommentError contained +".*+ syn match vimEnvvar "\$\I\i*" syn match vimEnvvar "\${\I\i*}" -" In-String Specials: {{{2 +" Strings {{{2 +" ======= + +" In-String Specials: " Try to catch strings, if nothing else matches (therefore it must precede the others!) " vimEscapeBrace handles ["] []"] (ie. "s don't terminate string inside []) syn region vimEscapeBrace oneline contained transparent start="[^\\]\(\\\\\)*\[\zs\^\=\]\=" skip="\\\\\|\\\]" end="]"me=e-1 @@ -638,8 +640,6 @@ syn cluster vimStringGroup contains=vimEscape,vimEscapeBrace,vimPatSep,vimNotPat syn region vimString oneline keepend matchgroup=vimString start=+[^a-zA-Z>\\@]"+lc=1 skip=+\\\\\|\\"+ matchgroup=vimStringEnd end=+"+ nextgroup=vimSubscript contains=@vimStringGroup extend syn region vimString oneline matchgroup=vimString start=+[^a-zA-Z>\\@]'+lc=1 end=+'+ nextgroup=vimSubscript contains=vimQuoteEscape extend "syn region vimString oneline start="\s/\s*\A"lc=1 skip="\\\\\|\\+" end="/" contains=@vimStringGroup " see tst45.vim -syn match vimString contained +"[^"]*\\$+ skipnl nextgroup=vimStringCont -syn match vimStringCont contained +\(\\\\\|.\)\{-}[^\\]"+ syn match vimEscape contained "\\." " syn match vimEscape contained +\\[befnrt\"]+ @@ -680,9 +680,6 @@ syn match vimSubstFlags contained "[&cegiIlnpr#]\+" syn match vimSubstDelim contained "\\" syn match vimSubstPat contained "\\\ze[/?&]" contains=vimSubstDelim nextgroup=vimSubstRep4 -" 'String': {{{2 -syn match vimString "[^(,]'[^']\{-}\zs'" - " Marks, Registers, Addresses, Filters: {{{2 syn match vimMark "'[a-zA-Z0-9]\ze[-+,!]" nextgroup=vimFilter,vimMarkNumber,vimSubst1 syn match vimMark "'[[\]{}()<>]\ze[-+,!]" nextgroup=vimFilter,vimMarkNumber,vimSubst1 @@ -876,22 +873,22 @@ syn match vimMenutranslateComment +".*+ contained containedin=vimMenutranslate " Angle-Bracket Notation: (tnx to Michael Geddes) {{{2 " ====================== syn case ignore -syn match vimNotation "\%#=1\%(\\\|<lt>\)\=<\%([scamd]-\)\{0,4}x\=\%(f\d\{1,2}\|[^ \t:]\|space\|bar\|bslash\|nl\|newline\|lf\|linefeed\|cr\|retu\%[rn]\|enter\|k\=del\%[ete]\|bs\|backspace\|tab\|esc\|csi\|right\|paste\%(start\|end\)\|left\|help\|undo\|k\=insert\|ins\|mouse\|[kz]\=home\|[kz]\=end\|kplus\|kminus\|kdivide\|kmultiply\|kenter\|kpoint\|space\|k\=\%(page\)\=\%(\|down\|up\|k\d\>\)\)>" contains=vimBracket - -syn match vimNotation "\%#=1\%(\\\|<lt>\)\=<\%([scamd2-4]-\)\{0,4}\%(net\|dec\|jsb\|pterm\|urxvt\|sgr\)mouse>" contains=vimBracket -syn match vimNotation "\%#=1\%(\\\|<lt>\)\=<\%([scamd2-4]-\)\{0,4}\%(left\|middle\|right\)\%(mouse\|drag\|release\)>" contains=vimBracket -syn match vimNotation "\%#=1\%(\\\|<lt>\)\=<\%([scamd2-4]-\)\{0,4}left\%(mouse\|release\)nm>" contains=vimBracket -syn match vimNotation "\%#=1\%(\\\|<lt>\)\=<\%([scamd2-4]-\)\{0,4}x[12]\%(mouse\|drag\|release\)>" contains=vimBracket -syn match vimNotation "\%#=1\%(\\\|<lt>\)\=<\%([scamd2-4]-\)\{0,4}sgrmouserelease>" contains=vimBracket -syn match vimNotation "\%#=1\%(\\\|<lt>\)\=<\%([scamd2-4]-\)\{0,4}mouse\%(up\|down\|move\)>" contains=vimBracket -syn match vimNotation "\%#=1\%(\\\|<lt>\)\=<\%([scamd2-4]-\)\{0,4}scrollwheel\%(up\|down\|right\|left\)>" contains=vimBracket - -syn match vimNotation "\%#=1\%(\\\|<lt>\)\=<\%(sid\|nop\|nul\|lt\|drop\)>" contains=vimBracket -syn match vimNotation "\%#=1\%(\\\|<lt>\)\=<\%(snr\|plug\|cursorhold\|ignore\|cmd\|scriptcmd\|focus\%(gained\|lost\)\)>" contains=vimBracket -syn match vimNotation '\%(\\\|<lt>\)\=<C-R>[0-9a-z"%#:.\-=]'he=e-1 contains=vimBracket -syn match vimNotation '\%#=1\%(\\\|<lt>\)\=<\%(q-\)\=\%(line[12]\|count\|bang\|reg\|args\|mods\|f-args\|f-mods\|lt\)>' contains=vimBracket -syn match vimNotation "\%#=1\%(\\\|<lt>\)\=<\%([cas]file\|abuf\|amatch\|cexpr\|cword\|cWORD\|client\|stack\|script\|sf\=lnum\)>" contains=vimBracket -syn match vimNotation "\%#=1\%(\\\|<lt>\)\=<\%([scamd]-\)\{0,4}char-\%(\d\+\|0\o\+\|0x\x\+\)>" contains=vimBracket +syn match vimNotation contained "\%#=1\%(\\\|<lt>\)\=<\%([scamd]-\)\{0,4}x\=\%(f\d\{1,2}\|[^ \t:]\|space\|bar\|bslash\|nl\|newline\|lf\|linefeed\|cr\|retu\%[rn]\|enter\|k\=del\%[ete]\|bs\|backspace\|tab\|esc\|csi\|right\|paste\%(start\|end\)\|left\|help\|undo\|k\=insert\|ins\|mouse\|[kz]\=home\|[kz]\=end\|kplus\|kminus\|kdivide\|kmultiply\|kenter\|kpoint\|space\|k\=\%(page\)\=\%(\|down\|up\|k\d\>\)\)>" contains=vimBracket + +syn match vimNotation contained "\%#=1\%(\\\|<lt>\)\=<\%([scamd2-4]-\)\{0,4}\%(net\|dec\|jsb\|pterm\|urxvt\|sgr\)mouse>" contains=vimBracket +syn match vimNotation contained "\%#=1\%(\\\|<lt>\)\=<\%([scamd2-4]-\)\{0,4}\%(left\|middle\|right\)\%(mouse\|drag\|release\)>" contains=vimBracket +syn match vimNotation contained "\%#=1\%(\\\|<lt>\)\=<\%([scamd2-4]-\)\{0,4}left\%(mouse\|release\)nm>" contains=vimBracket +syn match vimNotation contained "\%#=1\%(\\\|<lt>\)\=<\%([scamd2-4]-\)\{0,4}x[12]\%(mouse\|drag\|release\)>" contains=vimBracket +syn match vimNotation contained "\%#=1\%(\\\|<lt>\)\=<\%([scamd2-4]-\)\{0,4}sgrmouserelease>" contains=vimBracket +syn match vimNotation contained "\%#=1\%(\\\|<lt>\)\=<\%([scamd2-4]-\)\{0,4}mouse\%(up\|down\|move\)>" contains=vimBracket +syn match vimNotation contained "\%#=1\%(\\\|<lt>\)\=<\%([scamd2-4]-\)\{0,4}scrollwheel\%(up\|down\|right\|left\)>" contains=vimBracket + +syn match vimNotation contained "\%#=1\%(\\\|<lt>\)\=<\%(sid\|nop\|nul\|lt\|drop\)>" contains=vimBracket +syn match vimNotation contained "\%#=1\%(\\\|<lt>\)\=<\%(snr\|plug\|cursorhold\|ignore\|cmd\|scriptcmd\|focus\%(gained\|lost\)\)>" contains=vimBracket +syn match vimNotation contained '\%(\\\|<lt>\)\=<C-R>[0-9a-z"%#:.\-=]'he=e-1 contains=vimBracket +syn match vimNotation contained '\%#=1\%(\\\|<lt>\)\=<\%(q-\)\=\%(line[12]\|count\|bang\|reg\|args\|mods\|f-args\|f-mods\|lt\)>' contains=vimBracket +syn match vimNotation contained "\%#=1\%(\\\|<lt>\)\=<\%([cas]file\|abuf\|amatch\|cexpr\|cword\|cWORD\|client\|stack\|script\|sf\=lnum\)>" contains=vimBracket +syn match vimNotation contained "\%#=1\%(\\\|<lt>\)\=<\%([scamd]-\)\{0,4}char-\%(\d\+\|0\o\+\|0x\x\+\)>" contains=vimBracket syn match vimBracket contained "[\\<>]" syn case match @@ -917,7 +914,7 @@ if !exists("g:vimsyn_noerror") && !exists("g:vimsyn_novimfunctionerror") syn match vimBufnrWarn /\<bufnr\s*(\s*["']\.['"]\s*)/ endif -syn match vimNotFunc "\<if\>\|\<el\%[seif]\>\|\<retu\%[rn]\>\|\<while\>" skipwhite nextgroup=@vimExprList,vimNotation +syn match vimNotFunc "\%#=1\<\%(if\|el\%[seif]\|retu\%[rn]\|while\)\>" skipwhite nextgroup=@vimExprList,vimNotation " Match: {{{2 " ===== diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 8b31196eef..ef09dbb0aa 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -445,10 +445,14 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// placed below the buffer line containing the mark. /// /// - virt_lines_above: place virtual lines above instead. -/// - virt_lines_leftcol: Place extmarks in the leftmost +/// - virt_lines_leftcol: Place virtual lines in the leftmost /// column of the window, bypassing /// sign and number columns. -/// +/// - virt_lines_overflow: controls how to handle virtual lines wider +/// than the window. Currently takes the one of the following values: +/// - "trunc": truncate virtual lines on the right (default). +/// - "scroll": virtual lines can scroll horizontally with 'nowrap', +/// otherwise the same as "trunc". /// - ephemeral : for use with |nvim_set_decoration_provider()| /// callbacks. The mark will only be used for the current /// redraw cycle, and not be permantently stored in the @@ -669,7 +673,17 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer } } - bool virt_lines_leftcol = opts->virt_lines_leftcol; + int virt_lines_flags = opts->virt_lines_leftcol ? kVLLeftcol : 0; + if (HAS_KEY(opts, set_extmark, virt_lines_overflow)) { + String str = opts->virt_lines_overflow; + if (strequal("scroll", str.data)) { + virt_lines_flags |= kVLScroll; + } else if (!strequal("trunc", str.data)) { + VALIDATE_S(false, "virt_lines_overflow", str.data, { + goto error; + }); + } + } if (HAS_KEY(opts, set_extmark, virt_lines)) { Array a = opts->virt_lines; @@ -679,7 +693,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer }); int dummig; VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig); - kv_push(virt_lines.data.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol })); + kv_push(virt_lines.data.virt_lines, ((struct virt_line){ jtem, virt_lines_flags })); if (ERROR_SET(err)) { goto error; } diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 6625908cda..b3015911f9 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -45,6 +45,7 @@ typedef struct { Array virt_lines; Boolean virt_lines_above; Boolean virt_lines_leftcol; + String virt_lines_overflow; Boolean strict; String sign_text; HLGroupID sign_hl_group; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index c98635f8fd..a1af26e56f 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -123,13 +123,6 @@ void try_leave(const TryState *const tstate, Error *const err) discard_current_exception(); } - assert(msg_list == &tstate->private_msg_list); - assert(*msg_list == NULL); - assert(current_exception == NULL); - assert(!got_int); - assert(!did_throw); - assert(!need_rethrow); - assert(!did_emsg); // Restore the exception context. msg_list = (msglist_T **)tstate->msg_list; current_exception = tstate->current_exception; diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index d581c6bc10..5cf1ca4d34 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -64,7 +64,7 @@ #define NIL ((Object)OBJECT_INIT) #define NULL_STRING ((String)STRING_INIT) -#define HAS_KEY(d, typ, key) (((d)->is_set__##typ##_ & (1 << KEYSET_OPTIDX_##typ##__##key)) != 0) +#define HAS_KEY(d, typ, key) (((d)->is_set__##typ##_ & (1ULL << KEYSET_OPTIDX_##typ##__##key)) != 0) #define GET_BOOL_OR_TRUE(d, typ, key) (HAS_KEY(d, typ, key) ? (d)->key : true) @@ -75,7 +75,7 @@ kv_push_c(dict, ((KeyValuePair) { .key = cstr_as_string(k), .value = v })) #define PUT_KEY(d, typ, key, v) \ - do { (d).is_set__##typ##_ |= (1 << KEYSET_OPTIDX_##typ##__##key); (d).key = v; } while (0) + do { (d).is_set__##typ##_ |= (1ULL << KEYSET_OPTIDX_##typ##__##key); (d).key = v; } while (0) #define ADD(array, item) \ kv_push(array, item) diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index ad27823667..34ad734528 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -554,6 +554,7 @@ void do_augroup(char *arg, bool del_group) current_augroup = augroup_add(arg); } else { // ":aug": list the group names msg_start(); + msg_ext_set_kind("list_cmd"); String name; int value; @@ -855,6 +856,7 @@ void do_autocmd(exarg_T *eap, char *arg_in, int forceit) // Print header when showing autocommands. if (is_showing) { // Highlight title + msg_ext_set_kind("list_cmd"); msg_puts_title(_("\n--- Autocommands ---")); if (*arg == '*' || *arg == '|' || *arg == NUL) { diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 9e6877cbfa..5fec2f1211 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -305,6 +305,8 @@ int open_buffer(bool read_stdin, exarg_T *eap, int flags_arg) if (read_fifo) { curbuf->b_p_bin = save_bin; if (retval == OK) { + // don't add READ_FIFO here, otherwise we won't be able to + // detect the encoding retval = read_buffer(false, eap, flags); } } diff --git a/src/nvim/change.c b/src/nvim/change.c index cb5e2b0f65..27749576d7 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -804,9 +804,8 @@ void ins_char_bytes(char *buf, size_t charlen) /// Insert a string at the cursor position. /// Note: Does NOT handle Replace mode. /// Caller must have prepared for undo. -void ins_str(char *s) +void ins_str(char *s, size_t slen) { - int newlen = (int)strlen(s); linenr_T lnum = curwin->w_cursor.lnum; if (virtual_active(curwin) && curwin->w_cursor.coladd > 0) { @@ -817,17 +816,17 @@ void ins_str(char *s) char *oldp = ml_get(lnum); int oldlen = ml_get_len(lnum); - char *newp = xmalloc((size_t)oldlen + (size_t)newlen + 1); + char *newp = xmalloc((size_t)oldlen + slen + 1); if (col > 0) { memmove(newp, oldp, (size_t)col); } - memmove(newp + col, s, (size_t)newlen); + memmove(newp + col, s, slen); int bytes = oldlen - col + 1; assert(bytes >= 0); - memmove(newp + col + newlen, oldp + col, (size_t)bytes); + memmove(newp + col + slen, oldp + col, (size_t)bytes); ml_replace(lnum, newp, false); - inserted_bytes(lnum, col, 0, newlen); - curwin->w_cursor.col += newlen; + inserted_bytes(lnum, col, 0, (int)slen); + curwin->w_cursor.col += (int)slen; } // Delete one character under the cursor. diff --git a/src/nvim/channel.h b/src/nvim/channel.h index 81d67ceb66..d7149efcae 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -40,7 +40,7 @@ struct Channel { CallbackReader on_data; CallbackReader on_stderr; Callback on_exit; - int exit_status; + int exit_status; ///< Process exit-code (if the channel wraps a process). bool callback_busy; bool callback_scheduled; diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 3ca3f2904b..d41e7e32be 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -1169,15 +1169,17 @@ void decor_to_dict_legacy(Dict *dict, DecorInline decor, bool hl_name, Arena *ar if (virt_lines) { Array all_chunks = arena_array(arena, kv_size(virt_lines->data.virt_lines)); - bool virt_lines_leftcol = false; + int virt_lines_flags = 0; for (size_t i = 0; i < kv_size(virt_lines->data.virt_lines); i++) { - virt_lines_leftcol = kv_A(virt_lines->data.virt_lines, i).left_col; + virt_lines_flags = kv_A(virt_lines->data.virt_lines, i).flags; Array chunks = virt_text_to_array(kv_A(virt_lines->data.virt_lines, i).line, hl_name, arena); ADD(all_chunks, ARRAY_OBJ(chunks)); } PUT_C(*dict, "virt_lines", ARRAY_OBJ(all_chunks)); PUT_C(*dict, "virt_lines_above", BOOLEAN_OBJ(virt_lines->flags & kVTLinesAbove)); - PUT_C(*dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol)); + PUT_C(*dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_flags & kVLLeftcol)); + PUT_C(*dict, "virt_lines_overflow", + CSTR_AS_OBJ(virt_lines_flags & kVLScroll ? "scroll" : "trunc")); priority = virt_lines->priority; } diff --git a/src/nvim/decoration_defs.h b/src/nvim/decoration_defs.h index 36ad6df7a0..8be988cd82 100644 --- a/src/nvim/decoration_defs.h +++ b/src/nvim/decoration_defs.h @@ -26,7 +26,14 @@ typedef enum { kVPosWinCol, } VirtTextPos; -typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines; +/// Flags for virtual lines +enum { + kVLLeftcol = 1, ///< Start at left window edge, ignoring number column, etc. + kVLScroll = 2, ///< Can scroll horizontally with 'nowrap' + // kVLWrap = 4, +}; + +typedef kvec_t(struct virt_line { VirtText line; int flags; }) VirtLines; typedef uint16_t DecorPriority; #define DECOR_PRIORITY_BASE 0x1000 diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 0988f668d5..884abb6b88 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -231,7 +231,8 @@ static int line_putchar(buf_T *buf, const char **pp, schar_T *dest, int maxcells } if (*p == TAB) { - cells = MIN(tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array), maxcells); + cells = tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array); + cells = MIN(cells, maxcells); } // When overwriting the left half of a double-width char, clear the right half. @@ -345,7 +346,7 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int if (vt) { int vcol = item->draw_col - col_off; int col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text, - vt->hl_mode, max_col, vcol); + vt->hl_mode, max_col, vcol, 0); if (do_eol && ((vt->pos == kVPosEndOfLine) || (vt->pos == kVPosEndOfLineRightAlign))) { state->eol_col = col + 1; } @@ -358,32 +359,45 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int } static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col, - int vcol) + int vcol, int skip_cells) { - const char *p = ""; + const char *virt_str = ""; int virt_attr = 0; size_t virt_pos = 0; while (col < max_col) { - if (!*p) { + if (skip_cells >= 0 && *virt_str == NUL) { if (virt_pos >= kv_size(vt)) { break; } virt_attr = 0; - p = next_virt_text_chunk(vt, &virt_pos, &virt_attr); - if (p == NULL) { + virt_str = next_virt_text_chunk(vt, &virt_pos, &virt_attr); + if (virt_str == NULL) { break; } } - if (*p == NUL) { + // Skip cells in the text. + while (skip_cells > 0 && *virt_str != NUL) { + int c_len = utfc_ptr2len(virt_str); + int cells = *virt_str == TAB + ? tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array) + : utf_ptr2cells(virt_str); + skip_cells -= cells; + vcol += cells; + virt_str += c_len; + } + // If a double-width char or TAB doesn't fit, pad with spaces. + const char *draw_str = skip_cells < 0 ? " " : virt_str; + if (*draw_str == NUL) { continue; } + assert(skip_cells <= 0); int attr; bool through = false; if (hl_mode == kHlModeCombine) { attr = hl_combine_attr(linebuf_attr[col], virt_attr); } else if (hl_mode == kHlModeBlend) { - through = (*p == ' '); + through = (*draw_str == ' '); attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); } else { attr = virt_attr; @@ -397,13 +411,18 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, // Clear the right half as well for the assertion in line_putchar(). linebuf_char[col] = schar_from_ascii(' '); } - int cells = line_putchar(buf, &p, through ? dummy : &linebuf_char[col], + int cells = line_putchar(buf, &draw_str, through ? dummy : &linebuf_char[col], maxcells, vcol); for (int c = 0; c < cells; c++) { linebuf_attr[col] = attr; col++; } - vcol += cells; + if (skip_cells < 0) { + skip_cells++; + } else { + vcol += cells; + virt_str = draw_str; + } } return col; } @@ -911,28 +930,22 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t if (wlv->skip_cells > 0) { int virt_text_width = (int)mb_string2cells(wlv->p_extra); if (virt_text_width > wlv->skip_cells) { - int cells_to_skip = wlv->skip_cells; + int skip_cells_remaining = wlv->skip_cells; // Skip cells in the text. - while (cells_to_skip > 0) { + while (skip_cells_remaining > 0) { + int cells = utf_ptr2cells(wlv->p_extra); + if (cells > skip_cells_remaining) { + break; + } int c_len = utfc_ptr2len(wlv->p_extra); - cells_to_skip -= utf_ptr2cells(wlv->p_extra); + skip_cells_remaining -= cells; wlv->p_extra += c_len; wlv->n_extra -= c_len; wlv->n_attr--; } - // If a double-width char doesn't fit, pad with space. - if (cells_to_skip < 0) { - int pad_len = -cells_to_skip; - char *padded = get_extra_buf((size_t)(wlv->n_extra + pad_len) + 1); - memset(padded, ' ', (size_t)pad_len); - xmemcpyz(padded + pad_len, wlv->p_extra, (size_t)wlv->n_extra); - wlv->p_extra = padded; - wlv->n_extra += pad_len; - wlv->n_attr += pad_len; - } // Skipped cells needed to be accounted for in vcol. - wlv->skipped_cells += wlv->skip_cells; - wlv->skip_cells = 0; + wlv->skipped_cells += wlv->skip_cells - skip_cells_remaining; + wlv->skip_cells = skip_cells_remaining; } else { // The whole text is left of the window, drop // it and advance to the next one. @@ -1057,6 +1070,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s bool in_multispace = false; // in multiple consecutive spaces int multispace_pos = 0; // position in lcs-multispace string + int n_extra_next = 0; // n_extra to use after current extra chars + int extra_attr_next = -1; // extra_attr to use after current extra chars + bool search_attr_from_match = false; // if search_attr is from :match bool has_decor = false; // this buffer has decoration @@ -1587,8 +1603,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s const bool may_have_inline_virt = !has_foldtext && buf_meta_total(wp->w_buffer, kMTMetaInline) > 0; - int virt_line_index; - int virt_line_offset = -1; + int virt_line_index = -1; + int virt_line_flags = 0; // Repeat for the whole displayed line. while (true) { int has_match_conc = 0; ///< match wants to conceal @@ -1616,11 +1632,11 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s if (index > 0) { virt_line_index = (int)kv_size(virt_lines) - index; assert(virt_line_index >= 0); - virt_line_offset = kv_A(virt_lines, virt_line_index).left_col ? 0 : win_col_off(wp); + virt_line_flags = kv_A(virt_lines, virt_line_index).flags; } } - if (virt_line_offset == 0) { + if (virt_line_index >= 0 && (virt_line_flags & kVLLeftcol)) { // skip columns } else if (statuscol.draw) { // Draw 'statuscolumn' if it is set. @@ -1931,7 +1947,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s if (wlv.col >= grid->cols - 1 && schar_cells(mb_schar) == 2) { mb_c = '>'; mb_l = 1; - (void)mb_l; mb_schar = schar_from_ascii(mb_c); multi_attr = win_hl_attr(wp, HLF_AT); @@ -1944,30 +1959,58 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s wlv.n_extra -= mb_l; wlv.p_extra += mb_l; } - } - // Only restore search_attr and area_attr after "n_extra" in - // the next screen line is also done. - if (wlv.n_extra <= 0) { - if (search_attr == 0) { - search_attr = saved_search_attr; - saved_search_attr = 0; - } - if (area_attr == 0 && *ptr != NUL) { - area_attr = saved_area_attr; - saved_area_attr = 0; - } - if (decor_attr == 0) { - decor_attr = saved_decor_attr; - saved_decor_attr = 0; + // If a double-width char doesn't fit at the left side display a '<'. + if (wlv.filler_todo <= 0 && wlv.skip_cells > 0 && mb_l > 1) { + if (wlv.n_extra > 0) { + n_extra_next = wlv.n_extra; + extra_attr_next = wlv.extra_attr; + } + wlv.n_extra = 1; + wlv.sc_extra = schar_from_ascii(MB_FILLER_CHAR); + wlv.sc_final = NUL; + mb_schar = schar_from_ascii(' '); + mb_c = ' '; + mb_l = 1; + (void)mb_l; + wlv.n_attr++; + wlv.extra_attr = win_hl_attr(wp, HLF_AT); } + } - if (wlv.extra_for_extmark) { - // wlv.extra_attr should be used at this position but not - // any further. + if (wlv.n_extra <= 0) { + // Only restore search_attr and area_attr when there is no "n_extra" to show. + if (n_extra_next <= 0) { + if (search_attr == 0) { + search_attr = saved_search_attr; + saved_search_attr = 0; + } + if (area_attr == 0 && *ptr != NUL) { + area_attr = saved_area_attr; + saved_area_attr = 0; + } + if (decor_attr == 0) { + decor_attr = saved_decor_attr; + saved_decor_attr = 0; + } + if (wlv.extra_for_extmark) { + // wlv.extra_attr should be used at this position but not any further. + wlv.reset_extra_attr = true; + extra_attr_next = -1; + } + wlv.extra_for_extmark = false; + } else { + assert(wlv.sc_extra != NUL || wlv.sc_final != NUL); + assert(wlv.p_extra != NULL); + wlv.sc_extra = NUL; + wlv.sc_final = NUL; + wlv.n_extra = n_extra_next; + n_extra_next = 0; + // wlv.extra_attr should be used at this position, but extra_attr_next + // should be used after that. wlv.reset_extra_attr = true; + assert(extra_attr_next >= 0); } - wlv.extra_for_extmark = false; } } else if (wlv.filler_todo > 0) { // Wait with reading text until filler lines are done. Still need to @@ -2544,36 +2587,49 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL; } - // Don't override visual selection highlighting. + // Use "wlv.extra_attr", but don't override visual selection highlighting. if (wlv.n_attr > 0 && !search_attr_from_match) { wlv.char_attr = hl_combine_attr(wlv.char_attr, wlv.extra_attr); if (wlv.reset_extra_attr) { wlv.reset_extra_attr = false; - wlv.extra_attr = 0; - // search_attr_from_match can be restored now that the extra_attr has been applied - search_attr_from_match = saved_search_attr_from_match; + if (extra_attr_next >= 0) { + wlv.extra_attr = extra_attr_next; + extra_attr_next = -1; + } else { + wlv.extra_attr = 0; + // search_attr_from_match can be restored now that the extra_attr has been applied + search_attr_from_match = saved_search_attr_from_match; + } } } // Handle the case where we are in column 0 but not on the first // character of the line and the user wants us to show us a - // special character (via 'listchars' option "precedes:<char>". + // special character (via 'listchars' option "precedes:<char>"). if (lcs_prec_todo != NUL && wp->w_p_list && (wp->w_p_wrap ? (wp->w_skipcol > 0 && wlv.row == 0) : wp->w_leftcol > 0) && wlv.filler_todo <= 0 + && wlv.skip_cells <= 0 && mb_schar != NUL) { - mb_schar = wp->w_p_lcs_chars.prec; lcs_prec_todo = NUL; if (schar_cells(mb_schar) > 1) { // Double-width character being overwritten by the "precedes" // character, need to fill up half the character. wlv.sc_extra = schar_from_ascii(MB_FILLER_CHAR); wlv.sc_final = NUL; + if (wlv.n_extra > 0) { + assert(wlv.p_extra != NULL); + n_extra_next = wlv.n_extra; + extra_attr_next = wlv.extra_attr; + wlv.n_attr = MAX(wlv.n_attr + 1, 2); + } else { + wlv.n_attr = 2; + } wlv.n_extra = 1; - wlv.n_attr = 2; wlv.extra_attr = win_hl_attr(wp, HLF_AT); } + mb_schar = wp->w_p_lcs_chars.prec; mb_c = schar_get_first_codepoint(mb_schar); saved_attr3 = wlv.char_attr; // save current attr wlv.char_attr = win_hl_attr(wp, HLF_AT); // overwriting char_attr @@ -2715,7 +2771,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s } if (kv_size(fold_vt) > 0) { - draw_virt_text_item(buf, win_col_offset, fold_vt, kHlModeCombine, grid->cols, 0); + draw_virt_text_item(buf, win_col_offset, fold_vt, kHlModeCombine, grid->cols, 0, 0); } draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row); // Set increasing virtual columns in grid->vcols[] to set correct curswant @@ -2923,7 +2979,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s end_check: // At end of screen line and there is more to come: Display the line // so far. If there is no more to display it is caught above. - if (wlv.col >= grid->cols && (!has_foldtext || virt_line_offset >= 0) + if (wlv.col >= grid->cols && (!has_foldtext || virt_line_index >= 0) && (wlv.col <= leftcols_width || *ptr != NUL || wlv.filler_todo > 0 @@ -2956,9 +3012,14 @@ end_check: } } - if (virt_line_offset >= 0) { - draw_virt_text_item(buf, virt_line_offset, kv_A(virt_lines, virt_line_index).line, - kHlModeReplace, grid->cols, 0); + if (virt_line_index >= 0) { + draw_virt_text_item(buf, + virt_line_flags & kVLLeftcol ? 0 : win_col_offset, + kv_A(virt_lines, virt_line_index).line, + kHlModeReplace, + grid->cols, + 0, + virt_line_flags & kVLScroll ? wp->w_leftcol : 0); } else if (wlv.filler_todo <= 0) { draw_virt_text(wp, buf, win_col_offset, &draw_col, wlv.row); } @@ -3008,7 +3069,8 @@ end_check: statuscol.draw = false; // don't draw status column if "n" is in 'cpo' } wlv.filler_todo--; - virt_line_offset = -1; + virt_line_index = -1; + virt_line_flags = 0; // When the filler lines are actually below the last line of the // file, don't draw the line itself, break here. if (wlv.filler_todo == 0 && (wp->w_botfill || end_fill)) { diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 4d7f80bf76..162bcb910b 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -2695,18 +2695,36 @@ void redraw_buf_line_later(buf_T *buf, linenr_T line, bool force) } } -void redraw_buf_range_later(buf_T *buf, linenr_T firstline, linenr_T lastline) +void redraw_win_range_later(win_T *wp, linenr_T first, linenr_T last) +{ + if (last >= wp->w_topline && first < wp->w_botline) { + if (wp->w_redraw_top == 0 || wp->w_redraw_top > first) { + wp->w_redraw_top = first; + } + if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < last) { + wp->w_redraw_bot = last; + } + redraw_later(wp, UPD_VALID); + } +} + +/// Changed something in the current window, at buffer line "lnum", that +/// requires that line and possibly other lines to be redrawn. +/// Used when entering/leaving Insert mode with the cursor on a folded line. +/// Used to remove the "$" from a change command. +/// Note that when also inserting/deleting lines w_redraw_top and w_redraw_bot +/// may become invalid and the whole window will have to be redrawn. +void redrawWinline(win_T *wp, linenr_T lnum) + FUNC_ATTR_NONNULL_ALL +{ + redraw_win_range_later(wp, lnum, lnum); +} + +void redraw_buf_range_later(buf_T *buf, linenr_T first, linenr_T last) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf - && lastline >= wp->w_topline && firstline < wp->w_botline) { - if (wp->w_redraw_top == 0 || wp->w_redraw_top > firstline) { - wp->w_redraw_top = firstline; - } - if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lastline) { - wp->w_redraw_bot = lastline; - } - redraw_later(wp, UPD_VALID); + if (wp->w_buffer == buf) { + redraw_win_range_later(wp, first, last); } } } @@ -2805,27 +2823,6 @@ void win_redraw_last_status(const frame_T *frp) } } -/// Changed something in the current window, at buffer line "lnum", that -/// requires that line and possibly other lines to be redrawn. -/// Used when entering/leaving Insert mode with the cursor on a folded line. -/// Used to remove the "$" from a change command. -/// Note that when also inserting/deleting lines w_redraw_top and w_redraw_bot -/// may become invalid and the whole window will have to be redrawn. -void redrawWinline(win_T *wp, linenr_T lnum) - FUNC_ATTR_NONNULL_ALL -{ - if (lnum >= wp->w_topline - && lnum < wp->w_botline) { - if (wp->w_redraw_top == 0 || wp->w_redraw_top > lnum) { - wp->w_redraw_top = lnum; - } - if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) { - wp->w_redraw_bot = lnum; - } - redraw_later(wp, UPD_VALID); - } -} - /// Return true if the cursor line in window "wp" may be concealed, according /// to the 'concealcursor' option. bool conceal_cursor_line(const win_T *wp) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 9e17c93f3f..6277cd83b6 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -562,10 +562,10 @@ static int insert_execute(VimState *state, int key) // Special handling of keys while the popup menu is visible or wanted // and the cursor is still in the completed word. Only when there is // a match, skip this when no matches were found. - if (ins_compl_active() - && pum_wanted() - && curwin->w_cursor.col >= ins_compl_col() - && ins_compl_has_shown_match()) { + bool ins_completion = ins_compl_active() + && curwin->w_cursor.col >= ins_compl_col() + && ins_compl_has_shown_match(); + if (ins_completion && pum_wanted()) { // BS: Delete one character from "compl_leader". if ((s->c == K_BS || s->c == Ctrl_H) && curwin->w_cursor.col > ins_compl_col() @@ -615,6 +615,8 @@ static int insert_execute(VimState *state, int key) ins_compl_delete(false); } } + } else if (ins_completion && !pum_wanted() && ins_compl_preinsert_effect()) { + ins_compl_delete(false); } // Prepare for or stop CTRL-X mode. This doesn't do completion, but it does @@ -1735,11 +1737,11 @@ void change_indent(int type, int amount, int round, bool call_changed_bytes) // the right screen column. if (vcol != (int)curwin->w_virtcol) { curwin->w_cursor.col = (colnr_T)new_cursor_col; - size_t i = (size_t)(curwin->w_virtcol - vcol); - char *ptr = xmallocz(i); - memset(ptr, ' ', i); - new_cursor_col += (int)i; - ins_str(ptr); + const size_t ptrlen = (size_t)(curwin->w_virtcol - vcol); + char *ptr = xmallocz(ptrlen); + memset(ptr, ' ', ptrlen); + new_cursor_col += (int)ptrlen; + ins_str(ptr, ptrlen); xfree(ptr); } @@ -2012,7 +2014,7 @@ static void insert_special(int c, int allow_modmask, int ctrlv) return; } p[len - 1] = NUL; - ins_str(p); + ins_str(p, (size_t)(len - 1)); AppendToRedobuffLit(p, -1); ctrlv = false; } @@ -2193,7 +2195,7 @@ void insertchar(int c, int flags, int second_indent) do_digraph(-1); // clear digraphs do_digraph((uint8_t)buf[i - 1]); // may be the start of a digraph buf[i] = NUL; - ins_str(buf); + ins_str(buf, (size_t)i); if (flags & INSCHAR_CTRLV) { redo_literal((uint8_t)(*buf)); i = 1; @@ -3858,7 +3860,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) if (State & VREPLACE_FLAG) { ins_char(' '); } else { - ins_str(" "); + ins_str(S_LEN(" ")); if ((State & REPLACE_FLAG)) { replace_push_nul(); } @@ -4268,7 +4270,7 @@ static bool ins_tab(void) if (State & VREPLACE_FLAG) { ins_char(' '); } else { - ins_str(" "); + ins_str(S_LEN(" ")); if (State & REPLACE_FLAG) { // no char replaced replace_push_nul(); } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index e454987e2b..1bb2bea502 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -2481,7 +2481,8 @@ M.funcs = { When {expr3} is omitted then "force" is assumed. {expr1} is changed when {expr2} is not empty. If necessary - make a copy of {expr1} first. + make a copy of {expr1} first or use |extendnew()| to return a + new List/Dictionary. {expr2} remains unchanged. When {expr1} is locked and {expr2} is not empty the operation fails. diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 8e83b3d146..7cec2aaa06 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3556,7 +3556,7 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - msg_ext_set_kind("list_cmd"); + msg_ext_set_kind("confirm"); msg_start(); msg_row = Rows - 1; // for when 'cmdheight' > 1 lines_left = Rows; // avoid more prompt diff --git a/src/nvim/event/proc.c b/src/nvim/event/proc.c index e32bbbc29a..1a5e01eb9d 100644 --- a/src/nvim/event/proc.c +++ b/src/nvim/event/proc.c @@ -437,11 +437,6 @@ static void on_proc_exit(Proc *proc) Loop *loop = proc->loop; ILOG("child exited: pid=%d status=%d" PRIu64, proc->pid, proc->status); - // XXX: This assumes the TUI never spawns any other processes...? - if (ui_client_channel_id) { - exit_on_closed_chan(proc->status); - } - // Process has terminated, but there could still be data to be read from the // OS. We are still in the libuv loop, so we cannot call code that polls for // more data directly. Instead delay the reading after the libuv loop by diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 1e6153bf8d..8ca0676a0f 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -50,6 +50,8 @@ #include <stdlib.h> #include <string.h> +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/autocmd_defs.h" @@ -80,7 +82,7 @@ #include "nvim/strings.h" #include "nvim/vim_defs.h" -static char *ff_expand_buffer = NULL; // used for expanding filenames +static String ff_expand_buffer = STRING_INIT; // used for expanding filenames // type for the directory search stack typedef struct ff_stack { @@ -88,8 +90,8 @@ typedef struct ff_stack { // the fix part (no wildcards) and the part containing the wildcards // of the search path - char *ffs_fix_path; - char *ffs_wc_path; + String ffs_fix_path; + String ffs_wc_path; // files/dirs found in the above directory, matched by the first wildcard // of wc_part @@ -172,12 +174,12 @@ typedef struct { ff_visited_list_hdr_T *ffsc_dir_visited_list; ff_visited_list_hdr_T *ffsc_visited_lists_list; ff_visited_list_hdr_T *ffsc_dir_visited_lists_list; - char *ffsc_file_to_search; - char *ffsc_start_dir; - char *ffsc_fix_path; - char *ffsc_wc_path; + String ffsc_file_to_search; + String ffsc_start_dir; + String ffsc_fix_path; + String ffsc_wc_path; int ffsc_level; - char **ffsc_stopdirs_v; + String *ffsc_stopdirs_v; int ffsc_find_what; int ffsc_tagfile; } ff_search_ctx_T; @@ -244,8 +246,9 @@ static const char e_path_too_long_for_completion[] /// /// @param tagfile expanding names of tags files /// @param rel_fname file name to use for "." -void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, int free_visited, - int find_what, void *search_ctx_arg, int tagfile, char *rel_fname) +void *vim_findfile_init(char *path, char *filename, size_t filenamelen, char *stopdirs, int level, + int free_visited, int find_what, void *search_ctx_arg, int tagfile, + char *rel_fname) { ff_stack_T *sptr; ff_search_ctx_T *search_ctx; @@ -270,20 +273,23 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i // Reuse old visited lists. Get the visited list for the given // filename. If no list for the current filename exists, creates a new // one. - search_ctx->ffsc_visited_list = ff_get_visited_list(filename, - &search_ctx->ffsc_visited_lists_list); + search_ctx->ffsc_visited_list + = ff_get_visited_list(filename, filenamelen, + &search_ctx->ffsc_visited_lists_list); if (search_ctx->ffsc_visited_list == NULL) { goto error_return; } - search_ctx->ffsc_dir_visited_list = ff_get_visited_list(filename, - &search_ctx->ffsc_dir_visited_lists_list); + search_ctx->ffsc_dir_visited_list + = ff_get_visited_list(filename, filenamelen, + &search_ctx->ffsc_dir_visited_lists_list); if (search_ctx->ffsc_dir_visited_list == NULL) { goto error_return; } } - if (ff_expand_buffer == NULL) { - ff_expand_buffer = xmalloc(MAXPATHL); + if (ff_expand_buffer.data == NULL) { + ff_expand_buffer.size = 0; + ff_expand_buffer.data = xmalloc(MAXPATHL); } // Store information on starting dir now if path is relative. @@ -296,10 +302,11 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i if (!vim_isAbsName(rel_fname) && len + 1 < MAXPATHL) { // Make the start dir an absolute path name. - xmemcpyz(ff_expand_buffer, rel_fname, len); - search_ctx->ffsc_start_dir = FullName_save(ff_expand_buffer, false); + xmemcpyz(ff_expand_buffer.data, rel_fname, len); + ff_expand_buffer.size = len; + search_ctx->ffsc_start_dir = cstr_as_string(FullName_save(ff_expand_buffer.data, false)); } else { - search_ctx->ffsc_start_dir = xmemdupz(rel_fname, len); + search_ctx->ffsc_start_dir = cbuf_to_string(rel_fname, len); } if (*++path != NUL) { path++; @@ -313,25 +320,27 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i drive[0] = path[0]; drive[1] = ':'; drive[2] = NUL; - if (vim_FullName(drive, ff_expand_buffer, MAXPATHL, true) == FAIL) { + if (vim_FullName(drive, ff_expand_buffer.data, MAXPATHL, true) == FAIL) { goto error_return; } path += 2; } else #endif - if (os_dirname(ff_expand_buffer, MAXPATHL) == FAIL) { + if (os_dirname(ff_expand_buffer.data, MAXPATHL) == FAIL) { goto error_return; } + ff_expand_buffer.size = strlen(ff_expand_buffer.data); - search_ctx->ffsc_start_dir = xstrdup(ff_expand_buffer); + search_ctx->ffsc_start_dir = copy_string(ff_expand_buffer, NULL); #ifdef BACKSLASH_IN_FILENAME // A path that starts with "/dir" is relative to the drive, not to the // directory (but not for "//machine/dir"). Only use the drive name. if ((*path == '/' || *path == '\\') && path[1] != path[0] - && search_ctx->ffsc_start_dir[1] == ':') { - search_ctx->ffsc_start_dir[2] = NUL; + && search_ctx->ffsc_start_dir.data[1] == ':') { + search_ctx->ffsc_start_dir.data[2] = NUL; + search_ctx->ffsc_start_dir.size = 2; } #endif } @@ -351,12 +360,12 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i } size_t dircount = 1; - search_ctx->ffsc_stopdirs_v = xmalloc(sizeof(char *)); + search_ctx->ffsc_stopdirs_v = xmalloc(sizeof(String)); do { char *helper = walker; void *ptr = xrealloc(search_ctx->ffsc_stopdirs_v, - (dircount + 1) * sizeof(char *)); + (dircount + 1) * sizeof(String)); search_ctx->ffsc_stopdirs_v = ptr; walker = vim_strchr(walker, ';'); assert(!walker || walker - helper >= 0); @@ -364,17 +373,19 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i // "" means ascent till top of directory tree. if (*helper != NUL && !vim_isAbsName(helper) && len + 1 < MAXPATHL) { // Make the stop dir an absolute path name. - xmemcpyz(ff_expand_buffer, helper, len); - search_ctx->ffsc_stopdirs_v[dircount - 1] = FullName_save(helper, len); + xmemcpyz(ff_expand_buffer.data, helper, len); + ff_expand_buffer.size = len; + search_ctx->ffsc_stopdirs_v[dircount - 1] = cstr_as_string(FullName_save(helper, len)); } else { - search_ctx->ffsc_stopdirs_v[dircount - 1] = xmemdupz(helper, len); + search_ctx->ffsc_stopdirs_v[dircount - 1] = cbuf_to_string(helper, len); } if (walker) { walker++; } dircount++; } while (walker != NULL); - search_ctx->ffsc_stopdirs_v[dircount - 1] = NULL; + + search_ctx->ffsc_stopdirs_v[dircount - 1] = NULL_STRING; } search_ctx->ffsc_level = level; @@ -385,12 +396,11 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i char *wc_part = vim_strchr(path, '*'); if (wc_part != NULL) { int64_t llevel; - int len; char *errpt; // save the fix part of the path assert(wc_part - path >= 0); - search_ctx->ffsc_fix_path = xstrnsave(path, (size_t)(wc_part - path)); + search_ctx->ffsc_fix_path = cbuf_to_string(path, (size_t)(wc_part - path)); // copy wc_path and add restricts to the '**' wildcard. // The octet after a '**' is used as a (binary) counter. @@ -399,24 +409,24 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i // If no restrict is given after '**' the default is used. // Due to this technique the path looks awful if you print it as a // string. - len = 0; + ff_expand_buffer.size = 0; while (*wc_part != NUL) { - if (len + 5 >= MAXPATHL) { + if (ff_expand_buffer.size + 5 >= MAXPATHL) { emsg(_(e_path_too_long_for_completion)); break; } if (strncmp(wc_part, "**", 2) == 0) { - ff_expand_buffer[len++] = *wc_part++; - ff_expand_buffer[len++] = *wc_part++; + ff_expand_buffer.data[ff_expand_buffer.size++] = *wc_part++; + ff_expand_buffer.data[ff_expand_buffer.size++] = *wc_part++; llevel = strtol(wc_part, &errpt, 10); if (errpt != wc_part && llevel > 0 && llevel < 255) { - ff_expand_buffer[len++] = (char)llevel; + ff_expand_buffer.data[ff_expand_buffer.size++] = (char)llevel; } else if (errpt != wc_part && llevel == 0) { // restrict is 0 -> remove already added '**' - len -= 2; + ff_expand_buffer.size -= 2; } else { - ff_expand_buffer[len++] = FF_MAX_STAR_STAR_EXPAND; + ff_expand_buffer.data[ff_expand_buffer.size++] = FF_MAX_STAR_STAR_EXPAND; } wc_part = errpt; if (*wc_part != NUL && !vim_ispathsep(*wc_part)) { @@ -426,78 +436,108 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i goto error_return; } } else { - ff_expand_buffer[len++] = *wc_part++; + ff_expand_buffer.data[ff_expand_buffer.size++] = *wc_part++; } } - ff_expand_buffer[len] = NUL; - search_ctx->ffsc_wc_path = xstrdup(ff_expand_buffer); + ff_expand_buffer.data[ff_expand_buffer.size] = NUL; + search_ctx->ffsc_wc_path = copy_string(ff_expand_buffer, false); } else { - search_ctx->ffsc_fix_path = xstrdup(path); + search_ctx->ffsc_fix_path = cstr_to_string(path); } - if (search_ctx->ffsc_start_dir == NULL) { + if (search_ctx->ffsc_start_dir.data == NULL) { // store the fix part as startdir. // This is needed if the parameter path is fully qualified. - search_ctx->ffsc_start_dir = xstrdup(search_ctx->ffsc_fix_path); - search_ctx->ffsc_fix_path[0] = NUL; + search_ctx->ffsc_start_dir = copy_string(search_ctx->ffsc_fix_path, false); + search_ctx->ffsc_fix_path.data[0] = NUL; + search_ctx->ffsc_fix_path.size = 0; } // create an absolute path - if (strlen(search_ctx->ffsc_start_dir) - + strlen(search_ctx->ffsc_fix_path) + 3 >= MAXPATHL) { + if (search_ctx->ffsc_start_dir.size + + search_ctx->ffsc_fix_path.size + 3 >= MAXPATHL) { emsg(_(e_path_too_long_for_completion)); goto error_return; } - STRCPY(ff_expand_buffer, search_ctx->ffsc_start_dir); - add_pathsep(ff_expand_buffer); - { - size_t eb_len = strlen(ff_expand_buffer); - char *buf = xmalloc(eb_len + strlen(search_ctx->ffsc_fix_path) + 1); - STRCPY(buf, ff_expand_buffer); - STRCPY(buf + eb_len, search_ctx->ffsc_fix_path); + bool add_sep = !after_pathsep(search_ctx->ffsc_start_dir.data, + search_ctx->ffsc_start_dir.data + search_ctx->ffsc_start_dir.size); + ff_expand_buffer.size = (size_t)vim_snprintf(ff_expand_buffer.data, + MAXPATHL, + "%s%s", + search_ctx->ffsc_start_dir.data, + add_sep ? PATHSEPSTR : ""); + assert(ff_expand_buffer.size < MAXPATHL); + + { + size_t bufsize = ff_expand_buffer.size + search_ctx->ffsc_fix_path.size + 1; + char *buf = xmalloc(bufsize); + + vim_snprintf(buf, + bufsize, + "%s%s", + ff_expand_buffer.data, + search_ctx->ffsc_fix_path.data); if (os_isdir(buf)) { - strcat(ff_expand_buffer, search_ctx->ffsc_fix_path); - add_pathsep(ff_expand_buffer); + if (search_ctx->ffsc_fix_path.size > 0) { + add_sep = !after_pathsep(search_ctx->ffsc_fix_path.data, + search_ctx->ffsc_fix_path.data + search_ctx->ffsc_fix_path.size); + ff_expand_buffer.size += (size_t)vim_snprintf(ff_expand_buffer.data + ff_expand_buffer.size, + MAXPATHL - ff_expand_buffer.size, + "%s%s", + search_ctx->ffsc_fix_path.data, + add_sep ? PATHSEPSTR : ""); + assert(ff_expand_buffer.size < MAXPATHL); + } } else { - char *p = path_tail(search_ctx->ffsc_fix_path); - char *wc_path = NULL; - char *temp = NULL; - int len = 0; + char *p = path_tail(search_ctx->ffsc_fix_path.data); + int len = (int)search_ctx->ffsc_fix_path.size; - if (p > search_ctx->ffsc_fix_path) { + if (p > search_ctx->ffsc_fix_path.data) { // do not add '..' to the path and start upwards searching - len = (int)(p - search_ctx->ffsc_fix_path) - 1; - if ((len >= 2 && strncmp(search_ctx->ffsc_fix_path, "..", 2) == 0) - && (len == 2 || search_ctx->ffsc_fix_path[2] == PATHSEP)) { + len = (int)(p - search_ctx->ffsc_fix_path.data) - 1; + if ((len >= 2 && strncmp(search_ctx->ffsc_fix_path.data, "..", 2) == 0) + && (len == 2 || search_ctx->ffsc_fix_path.data[2] == PATHSEP)) { xfree(buf); goto error_return; } - xstrlcat(ff_expand_buffer, search_ctx->ffsc_fix_path, eb_len + (size_t)len + 1); - add_pathsep(ff_expand_buffer); - } else { - len = (int)strlen(search_ctx->ffsc_fix_path); + + add_sep = !after_pathsep(search_ctx->ffsc_fix_path.data, + search_ctx->ffsc_fix_path.data + search_ctx->ffsc_fix_path.size); + ff_expand_buffer.size += (size_t)vim_snprintf(ff_expand_buffer.data + ff_expand_buffer.size, + MAXPATHL - ff_expand_buffer.size, + "%.*s%s", + len, + search_ctx->ffsc_fix_path.data, + add_sep ? PATHSEPSTR : ""); + assert(ff_expand_buffer.size < MAXPATHL); } - if (search_ctx->ffsc_wc_path != NULL) { - wc_path = xstrdup(search_ctx->ffsc_wc_path); - temp = xmalloc(strlen(search_ctx->ffsc_wc_path) - + strlen(search_ctx->ffsc_fix_path + len) - + 1); - STRCPY(temp, search_ctx->ffsc_fix_path + len); - strcat(temp, search_ctx->ffsc_wc_path); - xfree(search_ctx->ffsc_wc_path); - xfree(wc_path); - search_ctx->ffsc_wc_path = temp; + if (search_ctx->ffsc_wc_path.data != NULL) { + size_t tempsize = (search_ctx->ffsc_fix_path.size - (size_t)len) + + search_ctx->ffsc_wc_path.size + 1; + char *temp = xmalloc(tempsize); + search_ctx->ffsc_wc_path.size = (size_t)vim_snprintf(temp, + tempsize, + "%s%s", + search_ctx->ffsc_fix_path.data + len, + search_ctx->ffsc_wc_path.data); + assert(search_ctx->ffsc_wc_path.size < tempsize); + xfree(search_ctx->ffsc_wc_path.data); + search_ctx->ffsc_wc_path.data = temp; } } xfree(buf); } - sptr = ff_create_stack_element(ff_expand_buffer, search_ctx->ffsc_wc_path, level, 0); + sptr = ff_create_stack_element(ff_expand_buffer.data, + ff_expand_buffer.size, + search_ctx->ffsc_wc_path.data, + search_ctx->ffsc_wc_path.size, + level, 0); ff_push(search_ctx, sptr); - search_ctx->ffsc_file_to_search = xstrdup(filename); + search_ctx->ffsc_file_to_search = cbuf_to_string(filename, filenamelen); return search_ctx; error_return: @@ -570,12 +610,9 @@ void vim_findfile_cleanup(void *ctx) /// NULL if nothing found. char *vim_findfile(void *search_ctx_arg) { - char *rest_of_wildcards; + String rest_of_wildcards; char *path_end = NULL; ff_stack_T *stackp = NULL; - size_t len; - char *p; - char *suf; if (search_ctx_arg == NULL) { return NULL; @@ -585,11 +622,11 @@ char *vim_findfile(void *search_ctx_arg) // filepath is used as buffer for various actions and as the storage to // return a found filename. - char *file_path = xmalloc(MAXPATHL); + String file_path = { .data = xmalloc(MAXPATHL) }; // store the end of the start dir -- needed for upward search - if (search_ctx->ffsc_start_dir != NULL) { - path_end = &search_ctx->ffsc_start_dir[strlen(search_ctx->ffsc_start_dir)]; + if (search_ctx->ffsc_start_dir.data != NULL) { + path_end = &search_ctx->ffsc_start_dir.data[search_ctx->ffsc_start_dir.size]; } // upward search loop @@ -627,12 +664,13 @@ char *vim_findfile(void *search_ctx_arg) // first time (hence stackp->ff_filearray == NULL) if (stackp->ffs_filearray == NULL && ff_check_visited(&search_ctx->ffsc_dir_visited_list->ffvl_visited_list, - stackp->ffs_fix_path, stackp->ffs_wc_path) == FAIL) { + stackp->ffs_fix_path.data, stackp->ffs_fix_path.size, + stackp->ffs_wc_path.data, stackp->ffs_wc_path.size) == FAIL) { #ifdef FF_VERBOSE if (p_verbose >= 5) { verbose_enter_scroll(); smsg(0, "Already Searched: %s (%s)", - stackp->ffs_fix_path, stackp->ffs_wc_path); + stackp->ffs_fix_path.data, stackp->ffs_wc_path.data); msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); } @@ -643,7 +681,7 @@ char *vim_findfile(void *search_ctx_arg) } else if (p_verbose >= 5) { verbose_enter_scroll(); smsg(0, "Searching: %s (%s)", - stackp->ffs_fix_path, stackp->ffs_wc_path); + stackp->ffs_fix_path.data, stackp->ffs_wc_path.data); msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); #endif @@ -655,7 +693,8 @@ char *vim_findfile(void *search_ctx_arg) continue; } - file_path[0] = NUL; + file_path.data[0] = NUL; + file_path.size = 0; // If no filearray till now expand wildcards // The function expand_wildcards() can handle an array of paths @@ -665,62 +704,79 @@ char *vim_findfile(void *search_ctx_arg) char *dirptrs[2]; // we use filepath to build the path expand_wildcards() should expand. - dirptrs[0] = file_path; + dirptrs[0] = file_path.data; dirptrs[1] = NULL; // if we have a start dir copy it in - if (!vim_isAbsName(stackp->ffs_fix_path) - && search_ctx->ffsc_start_dir) { - if (strlen(search_ctx->ffsc_start_dir) + 1 >= MAXPATHL) { + if (!vim_isAbsName(stackp->ffs_fix_path.data) + && search_ctx->ffsc_start_dir.data) { + if (search_ctx->ffsc_start_dir.size + 1 >= MAXPATHL) { ff_free_stack_element(stackp); goto fail; } - STRCPY(file_path, search_ctx->ffsc_start_dir); - if (!add_pathsep(file_path)) { + bool add_sep = !after_pathsep(search_ctx->ffsc_start_dir.data, + search_ctx->ffsc_start_dir.data + + search_ctx->ffsc_start_dir.size); + file_path.size = (size_t)vim_snprintf(file_path.data, + MAXPATHL, + "%s%s", + search_ctx->ffsc_start_dir.data, + add_sep ? PATHSEPSTR : ""); + if (file_path.size >= MAXPATHL) { ff_free_stack_element(stackp); goto fail; } } // append the fix part of the search path - if (strlen(file_path) + strlen(stackp->ffs_fix_path) + 1 >= MAXPATHL) { + if (file_path.size + stackp->ffs_fix_path.size + 1 >= MAXPATHL) { ff_free_stack_element(stackp); goto fail; } - strcat(file_path, stackp->ffs_fix_path); - if (!add_pathsep(file_path)) { + bool add_sep = !after_pathsep(stackp->ffs_fix_path.data, + stackp->ffs_fix_path.data + stackp->ffs_fix_path.size); + file_path.size += (size_t)vim_snprintf(file_path.data + file_path.size, + MAXPATHL - file_path.size, + "%s%s", + stackp->ffs_fix_path.data, + add_sep ? PATHSEPSTR : ""); + if (file_path.size >= MAXPATHL) { ff_free_stack_element(stackp); goto fail; } rest_of_wildcards = stackp->ffs_wc_path; - if (*rest_of_wildcards != NUL) { - len = strlen(file_path); - if (strncmp(rest_of_wildcards, "**", 2) == 0) { + if (*rest_of_wildcards.data != NUL) { + if (strncmp(rest_of_wildcards.data, "**", 2) == 0) { // pointer to the restrict byte // The restrict byte is not a character! - p = rest_of_wildcards + 2; + char *p = rest_of_wildcards.data + 2; if (*p > 0) { (*p)--; - if (len + 1 >= MAXPATHL) { + if (file_path.size + 1 >= MAXPATHL) { ff_free_stack_element(stackp); goto fail; } - file_path[len++] = '*'; + file_path.data[file_path.size++] = '*'; } if (*p == 0) { // remove '**<numb> from wildcards - STRMOVE(rest_of_wildcards, rest_of_wildcards + 3); + memmove(rest_of_wildcards.data, + rest_of_wildcards.data + 3, + (rest_of_wildcards.size - 3) + 1); // +1 for NUL + rest_of_wildcards.size -= 3; + stackp->ffs_wc_path.size = rest_of_wildcards.size; } else { - rest_of_wildcards += 3; + rest_of_wildcards.data += 3; + rest_of_wildcards.size -= 3; } if (stackp->ffs_star_star_empty == 0) { // if not done before, expand '**' to empty stackp->ffs_star_star_empty = 1; - dirptrs[1] = stackp->ffs_fix_path; + dirptrs[1] = stackp->ffs_fix_path.data; } } @@ -729,18 +785,20 @@ char *vim_findfile(void *search_ctx_arg) // still something else left. This is handled below by // pushing every directory returned from expand_wildcards() // on the stack again for further search. - while (*rest_of_wildcards - && !vim_ispathsep(*rest_of_wildcards)) { - if (len + 1 >= MAXPATHL) { + while (*rest_of_wildcards.data + && !vim_ispathsep(*rest_of_wildcards.data)) { + if (file_path.size + 1 >= MAXPATHL) { ff_free_stack_element(stackp); goto fail; } - file_path[len++] = *rest_of_wildcards++; + file_path.data[file_path.size++] = *rest_of_wildcards.data++; + rest_of_wildcards.size--; } - file_path[len] = NUL; - if (vim_ispathsep(*rest_of_wildcards)) { - rest_of_wildcards++; + file_path.data[file_path.size] = NUL; + if (vim_ispathsep(*rest_of_wildcards.data)) { + rest_of_wildcards.data++; + rest_of_wildcards.size--; } } @@ -748,7 +806,7 @@ char *vim_findfile(void *search_ctx_arg) // If the path is a URL don't try this. if (path_with_url(dirptrs[0])) { stackp->ffs_filearray = xmalloc(sizeof(char *)); - stackp->ffs_filearray[0] = xstrdup(dirptrs[0]); + stackp->ffs_filearray[0] = xmemdupz(dirptrs[0], file_path.size); stackp->ffs_filearray_size = 1; } else { // Add EW_NOTWILD because the expanded path may contain @@ -763,12 +821,13 @@ char *vim_findfile(void *search_ctx_arg) stackp->ffs_filearray_cur = 0; stackp->ffs_stage = 0; } else { - rest_of_wildcards = &stackp->ffs_wc_path[strlen(stackp->ffs_wc_path)]; + rest_of_wildcards.data = &stackp->ffs_wc_path.data[stackp->ffs_wc_path.size]; + rest_of_wildcards.size = 0; } if (stackp->ffs_stage == 0) { // this is the first time we work on this directory - if (*rest_of_wildcards == NUL) { + if (*rest_of_wildcards.data == NUL) { // We don't have further wildcards to expand, so we have to // check for the final file now. for (int i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; i++) { @@ -777,44 +836,46 @@ char *vim_findfile(void *search_ctx_arg) continue; // not a directory } // prepare the filename to be checked for existence below - if (strlen(stackp->ffs_filearray[i]) + 1 - + strlen(search_ctx->ffsc_file_to_search) >= MAXPATHL) { + size_t len = strlen(stackp->ffs_filearray[i]); + if (len + 1 + search_ctx->ffsc_file_to_search.size >= MAXPATHL) { ff_free_stack_element(stackp); goto fail; } - STRCPY(file_path, stackp->ffs_filearray[i]); - if (!add_pathsep(file_path)) { + bool add_sep = !after_pathsep(stackp->ffs_filearray[i], + stackp->ffs_filearray[i] + len); + file_path.size = (size_t)vim_snprintf(file_path.data, + MAXPATHL, + "%s%s%s", + stackp->ffs_filearray[i], + add_sep ? PATHSEPSTR : "", + search_ctx->ffsc_file_to_search.data); + if (file_path.size >= MAXPATHL) { ff_free_stack_element(stackp); goto fail; } - strcat(file_path, search_ctx->ffsc_file_to_search); // Try without extra suffix and then with suffixes // from 'suffixesadd'. - len = strlen(file_path); - if (search_ctx->ffsc_tagfile) { - suf = ""; - } else { - suf = curbuf->b_p_sua; - } + len = file_path.size; + char *suf = search_ctx->ffsc_tagfile ? "" : curbuf->b_p_sua; while (true) { // if file exists and we didn't already find it - if ((path_with_url(file_path) - || (os_path_exists(file_path) + if ((path_with_url(file_path.data) + || (os_path_exists(file_path.data) && (search_ctx->ffsc_find_what == FINDFILE_BOTH || ((search_ctx->ffsc_find_what == FINDFILE_DIR) - == os_isdir(file_path))))) + == os_isdir(file_path.data))))) #ifndef FF_VERBOSE && (ff_check_visited(&search_ctx->ffsc_visited_list->ffvl_visited_list, - file_path, "") == OK) + file_path.data, file_path.size, "", 0) == OK) #endif ) { #ifdef FF_VERBOSE if (ff_check_visited(&search_ctx->ffsc_visited_list->ffvl_visited_list, - file_path, "") == FAIL) { + file_path.data, file_path.size, "", 0) == FAIL) { if (p_verbose >= 5) { verbose_enter_scroll(); - smsg(0, "Already: %s", file_path); + smsg(0, "Already: %s", file_path.data); msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); } @@ -827,32 +888,37 @@ char *vim_findfile(void *search_ctx_arg) stackp->ffs_filearray_cur = i + 1; ff_push(search_ctx, stackp); - if (!path_with_url(file_path)) { - simplify_filename(file_path); + if (!path_with_url(file_path.data)) { + file_path.size = simplify_filename(file_path.data); } - if (os_dirname(ff_expand_buffer, MAXPATHL) == OK) { - p = path_shorten_fname(file_path, ff_expand_buffer); + + if (os_dirname(ff_expand_buffer.data, MAXPATHL) == OK) { + ff_expand_buffer.size = strlen(ff_expand_buffer.data); + char *p = path_shorten_fname(file_path.data, ff_expand_buffer.data); if (p != NULL) { - STRMOVE(file_path, p); + memmove(file_path.data, p, + (size_t)((file_path.data + file_path.size) - p) + 1); // +1 for NUL + file_path.size -= (size_t)(p - file_path.data); } } #ifdef FF_VERBOSE if (p_verbose >= 5) { verbose_enter_scroll(); - smsg(0, "HIT: %s", file_path); + smsg(0, "HIT: %s", file_path.data); msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); } #endif - return file_path; + return file_path.data; } // Not found or found already, try next suffix. if (*suf == NUL) { break; } - assert(MAXPATHL >= len); - copy_option_part(&suf, file_path + len, MAXPATHL - len, ","); + assert(MAXPATHL >= file_path.size); + file_path.size = len + copy_option_part(&suf, file_path.data + len, + MAXPATHL - len, ","); } } } else { @@ -863,7 +929,9 @@ char *vim_findfile(void *search_ctx_arg) } ff_push(search_ctx, ff_create_stack_element(stackp->ffs_filearray[i], - rest_of_wildcards, + strlen(stackp->ffs_filearray[i]), + rest_of_wildcards.data, + rest_of_wildcards.size, stackp->ffs_level - 1, 0)); } } @@ -873,11 +941,11 @@ char *vim_findfile(void *search_ctx_arg) // if wildcards contains '**' we have to descent till we reach the // leaves of the directory tree. - if (strncmp(stackp->ffs_wc_path, "**", 2) == 0) { + if (strncmp(stackp->ffs_wc_path.data, "**", 2) == 0) { for (int i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; i++) { if (path_fnamecmp(stackp->ffs_filearray[i], - stackp->ffs_fix_path) == 0) { + stackp->ffs_fix_path.data) == 0) { continue; // don't repush same directory } if (!os_isdir(stackp->ffs_filearray[i])) { @@ -885,7 +953,10 @@ char *vim_findfile(void *search_ctx_arg) } ff_push(search_ctx, ff_create_stack_element(stackp->ffs_filearray[i], - stackp->ffs_wc_path, stackp->ffs_level - 1, 1)); + strlen(stackp->ffs_filearray[i]), + stackp->ffs_wc_path.data, + stackp->ffs_wc_path.size, + stackp->ffs_level - 1, 1)); } } @@ -895,45 +966,58 @@ char *vim_findfile(void *search_ctx_arg) // If we reached this, we didn't find anything downwards. // Let's check if we should do an upward search. - if (search_ctx->ffsc_start_dir + if (search_ctx->ffsc_start_dir.data && search_ctx->ffsc_stopdirs_v != NULL && !got_int) { ff_stack_T *sptr; // path_end may point to the NUL or the previous path separator - ptrdiff_t plen = (path_end - search_ctx->ffsc_start_dir) + (*path_end != NUL); + ptrdiff_t plen = (path_end - search_ctx->ffsc_start_dir.data) + (*path_end != NUL); // is the last starting directory in the stop list? - if (ff_path_in_stoplist(search_ctx->ffsc_start_dir, + if (ff_path_in_stoplist(search_ctx->ffsc_start_dir.data, (size_t)plen, search_ctx->ffsc_stopdirs_v)) { break; } // cut of last dir - while (path_end > search_ctx->ffsc_start_dir && vim_ispathsep(*path_end)) { + while (path_end > search_ctx->ffsc_start_dir.data && vim_ispathsep(*path_end)) { path_end--; } - while (path_end > search_ctx->ffsc_start_dir && !vim_ispathsep(path_end[-1])) { + while (path_end > search_ctx->ffsc_start_dir.data && !vim_ispathsep(path_end[-1])) { path_end--; } - *path_end = 0; + *path_end = NUL; + + // we may have shortened search_ctx->ffsc_start_dir, so update it's length + search_ctx->ffsc_start_dir.size = (size_t)(path_end - search_ctx->ffsc_start_dir.data); path_end--; - if (*search_ctx->ffsc_start_dir == 0) { + if (*search_ctx->ffsc_start_dir.data == NUL) { break; } - if (strlen(search_ctx->ffsc_start_dir) + 1 - + strlen(search_ctx->ffsc_fix_path) >= MAXPATHL) { + if (search_ctx->ffsc_start_dir.size + 1 + + search_ctx->ffsc_fix_path.size >= MAXPATHL) { goto fail; } - STRCPY(file_path, search_ctx->ffsc_start_dir); - if (!add_pathsep(file_path)) { + bool add_sep = !after_pathsep(search_ctx->ffsc_start_dir.data, + search_ctx->ffsc_start_dir.data + + search_ctx->ffsc_start_dir.size); + file_path.size = (size_t)vim_snprintf(file_path.data, + MAXPATHL, + "%s%s%s", + search_ctx->ffsc_start_dir.data, + add_sep ? PATHSEPSTR : "", + search_ctx->ffsc_fix_path.data); + if (file_path.size >= MAXPATHL) { goto fail; } - strcat(file_path, search_ctx->ffsc_fix_path); // create a new stack entry - sptr = ff_create_stack_element(file_path, - search_ctx->ffsc_wc_path, search_ctx->ffsc_level, 0); + sptr = ff_create_stack_element(file_path.data, + file_path.size, + search_ctx->ffsc_wc_path.data, + search_ctx->ffsc_wc_path.size, + search_ctx->ffsc_level, 0); ff_push(search_ctx, sptr); } else { break; @@ -941,7 +1025,7 @@ char *vim_findfile(void *search_ctx_arg) } fail: - xfree(file_path); + xfree(file_path.data); return NULL; } @@ -988,7 +1072,7 @@ static void ff_free_visited_list(ff_visited_T *vl) /// @return the already visited list for the given filename. If none is found it /// allocates a new one. -static ff_visited_list_hdr_T *ff_get_visited_list(char *filename, +static ff_visited_list_hdr_T *ff_get_visited_list(char *filename, size_t filenamelen, ff_visited_list_hdr_T **list_headp) { ff_visited_list_hdr_T *retptr = NULL; @@ -1025,7 +1109,7 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char *filename, retptr = xmalloc(sizeof(*retptr)); retptr->ffvl_visited_list = NULL; - retptr->ffvl_filename = xstrdup(filename); + retptr->ffvl_filename = xmemdupz(filename, filenamelen); retptr->ffvl_next = *list_headp; *list_headp = retptr; @@ -1074,7 +1158,8 @@ static bool ff_wc_equal(char *s1, char *s2) /// /// @return FAIL if the given file/dir is already in the list or, /// OK if it is newly added -static int ff_check_visited(ff_visited_T **visited_list, char *fname, char *wc_path) +static int ff_check_visited(ff_visited_T **visited_list, char *fname, size_t fnamelen, + char *wc_path, size_t wc_pathlen) { ff_visited_T *vp; bool url = false; @@ -1083,10 +1168,12 @@ static int ff_check_visited(ff_visited_T **visited_list, char *fname, char *wc_p // For a URL we only compare the name, otherwise we compare the // device/inode. if (path_with_url(fname)) { - xstrlcpy(ff_expand_buffer, fname, MAXPATHL); + xmemcpyz(ff_expand_buffer.data, fname, fnamelen); + ff_expand_buffer.size = fnamelen; url = true; } else { - ff_expand_buffer[0] = NUL; + ff_expand_buffer.data[0] = NUL; + ff_expand_buffer.size = 0; if (!os_fileid(fname, &file_id)) { return FAIL; } @@ -1094,7 +1181,7 @@ static int ff_check_visited(ff_visited_T **visited_list, char *fname, char *wc_p // check against list of already visited files for (vp = *visited_list; vp != NULL; vp = vp->ffv_next) { - if ((url && path_fnamecmp(vp->ffv_fname, ff_expand_buffer) == 0) + if ((url && path_fnamecmp(vp->ffv_fname, ff_expand_buffer.data) == 0) || (!url && vp->file_id_valid && os_fileid_equal(&(vp->file_id), &file_id))) { // are the wildcard parts equal @@ -1106,7 +1193,7 @@ static int ff_check_visited(ff_visited_T **visited_list, char *fname, char *wc_p } // New file/dir. Add it to the list of visited files/dirs. - vp = xmalloc(offsetof(ff_visited_T, ffv_fname) + strlen(ff_expand_buffer) + 1); + vp = xmalloc(offsetof(ff_visited_T, ffv_fname) + ff_expand_buffer.size + 1); if (!url) { vp->file_id_valid = true; @@ -1114,11 +1201,11 @@ static int ff_check_visited(ff_visited_T **visited_list, char *fname, char *wc_p vp->ffv_fname[0] = NUL; } else { vp->file_id_valid = false; - STRCPY(vp->ffv_fname, ff_expand_buffer); + STRCPY(vp->ffv_fname, ff_expand_buffer.data); } if (wc_path != NULL) { - vp->ffv_wc_path = xstrdup(wc_path); + vp->ffv_wc_path = xmemdupz(wc_path, wc_pathlen); } else { vp->ffv_wc_path = NULL; } @@ -1130,8 +1217,8 @@ static int ff_check_visited(ff_visited_T **visited_list, char *fname, char *wc_p } /// create stack element from given path pieces -static ff_stack_T *ff_create_stack_element(char *fix_part, char *wc_part, int level, - int star_star_empty) +static ff_stack_T *ff_create_stack_element(char *fix_part, size_t fix_partlen, char *wc_part, + size_t wc_partlen, int level, int star_star_empty) { ff_stack_T *stack = xmalloc(sizeof(ff_stack_T)); @@ -1146,13 +1233,15 @@ static ff_stack_T *ff_create_stack_element(char *fix_part, char *wc_part, int le // the following saves NULL pointer checks in vim_findfile if (fix_part == NULL) { fix_part = ""; + fix_partlen = 0; } - stack->ffs_fix_path = xstrdup(fix_part); + stack->ffs_fix_path = cbuf_to_string(fix_part, fix_partlen); if (wc_part == NULL) { wc_part = ""; + wc_partlen = 0; } - stack->ffs_wc_path = xstrdup(wc_part); + stack->ffs_wc_path = cbuf_to_string(wc_part, wc_partlen); return stack; } @@ -1190,9 +1279,9 @@ static void ff_free_stack_element(ff_stack_T *const stack_ptr) return; } - // free handles possible NULL pointers - xfree(stack_ptr->ffs_fix_path); - xfree(stack_ptr->ffs_wc_path); + // API_CLEAR_STRING handles possible NULL pointers + API_CLEAR_STRING(stack_ptr->ffs_fix_path); + API_CLEAR_STRING(stack_ptr->ffs_wc_path); if (stack_ptr->ffs_filearray != NULL) { FreeWild(stack_ptr->ffs_filearray_size, stack_ptr->ffs_filearray); @@ -1211,34 +1300,28 @@ static void ff_clear(ff_search_ctx_T *search_ctx) ff_free_stack_element(sptr); } - xfree(search_ctx->ffsc_file_to_search); - xfree(search_ctx->ffsc_start_dir); - xfree(search_ctx->ffsc_fix_path); - xfree(search_ctx->ffsc_wc_path); - if (search_ctx->ffsc_stopdirs_v != NULL) { int i = 0; - while (search_ctx->ffsc_stopdirs_v[i] != NULL) { - xfree(search_ctx->ffsc_stopdirs_v[i]); + while (search_ctx->ffsc_stopdirs_v[i].data != NULL) { + xfree(search_ctx->ffsc_stopdirs_v[i].data); i++; } - xfree(search_ctx->ffsc_stopdirs_v); + XFREE_CLEAR(search_ctx->ffsc_stopdirs_v); } - search_ctx->ffsc_stopdirs_v = NULL; // reset everything - search_ctx->ffsc_file_to_search = NULL; - search_ctx->ffsc_start_dir = NULL; - search_ctx->ffsc_fix_path = NULL; - search_ctx->ffsc_wc_path = NULL; + API_CLEAR_STRING(search_ctx->ffsc_file_to_search); + API_CLEAR_STRING(search_ctx->ffsc_start_dir); + API_CLEAR_STRING(search_ctx->ffsc_fix_path); + API_CLEAR_STRING(search_ctx->ffsc_wc_path); search_ctx->ffsc_level = 0; } /// check if the given path is in the stopdirs /// /// @return true if yes else false -static bool ff_path_in_stoplist(char *path, size_t path_len, char **stopdirs_v) +static bool ff_path_in_stoplist(char *path, size_t path_len, String *stopdirs_v) { // eat up trailing path separators, except the first while (path_len > 1 && vim_ispathsep(path[path_len - 1])) { @@ -1250,13 +1333,13 @@ static bool ff_path_in_stoplist(char *path, size_t path_len, char **stopdirs_v) return true; } - for (int i = 0; stopdirs_v[i] != NULL; i++) { + for (int i = 0; stopdirs_v[i].data != NULL; i++) { // match for parent directory. So '/home' also matches // '/home/rks'. Check for PATHSEP in stopdirs_v[i], else // '/home/r' would also match '/home/rks' - if (path_fnamencmp(stopdirs_v[i], path, path_len) == 0 - && (strlen(stopdirs_v[i]) <= path_len - || vim_ispathsep(stopdirs_v[i][path_len]))) { + if (path_fnamencmp(stopdirs_v[i].data, path, path_len) == 0 + && (stopdirs_v[i].size <= path_len + || vim_ispathsep(stopdirs_v[i].data[path_len]))) { return true; } } @@ -1307,7 +1390,7 @@ char *find_file_in_path(char *ptr, size_t len, int options, int first, char *rel #if defined(EXITFREE) void free_findfile(void) { - XFREE_CLEAR(ff_expand_buffer); + API_CLEAR_STRING(ff_expand_buffer); } #endif @@ -1351,7 +1434,7 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch static char *dir; static bool did_findfile_init = false; char *file_name = NULL; - char *buf = NULL; + static size_t file_to_findlen = 0; if (rel_fname != NULL && path_with_url(rel_fname)) { // Do not attempt to search "relative" to a URL. #6009 @@ -1370,12 +1453,15 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch ptr[len] = save_char; xfree(*file_to_find); - *file_to_find = xstrdup(NameBuff); + file_to_findlen = strlen(NameBuff); + *file_to_find = xmemdupz(NameBuff, file_to_findlen); if (options & FNAME_UNESC) { // Change all "\ " to " ". for (ptr = *file_to_find; *ptr != NUL; ptr++) { if (ptr[0] == '\\' && ptr[1] == ' ') { - memmove(ptr, ptr + 1, strlen(ptr)); + memmove(ptr, ptr + 1, + (size_t)((*file_to_find + file_to_findlen) - (ptr + 1)) + 1); + file_to_findlen--; } } } @@ -1402,42 +1488,48 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch // filename on the first call. if (first == true) { if (path_with_url(*file_to_find)) { - file_name = xstrdup(*file_to_find); + file_name = xmemdupz(*file_to_find, file_to_findlen); goto theend; } + size_t rel_fnamelen = rel_fname != NULL ? strlen(rel_fname) : 0; + // When FNAME_REL flag given first use the directory of the file. // Otherwise or when this fails use the current directory. for (int run = 1; run <= 2; run++) { - size_t l = strlen(*file_to_find); + size_t l = file_to_findlen; if (run == 1 && rel_to_curdir && (options & FNAME_REL) && rel_fname != NULL - && strlen(rel_fname) + l < MAXPATHL) { - STRCPY(NameBuff, rel_fname); - STRCPY(path_tail(NameBuff), *file_to_find); - l = strlen(NameBuff); + && rel_fnamelen + l < MAXPATHL) { + l = (size_t)vim_snprintf(NameBuff, + MAXPATHL, + "%.*s%s", + (int)(path_tail(rel_fname) - rel_fname), + rel_fname, + *file_to_find); + assert(l < MAXPATHL); } else { STRCPY(NameBuff, *file_to_find); run = 2; } // When the file doesn't exist, try adding parts of 'suffixesadd'. - buf = suffixes; + size_t NameBufflen = l; + char *suffix = suffixes; while (true) { if ((os_path_exists(NameBuff) && (find_what == FINDFILE_BOTH - || ((find_what == FINDFILE_DIR) - == os_isdir(NameBuff))))) { - file_name = xstrdup(NameBuff); + || ((find_what == FINDFILE_DIR) == os_isdir(NameBuff))))) { + file_name = xmemdupz(NameBuff, NameBufflen); goto theend; } - if (*buf == NUL) { + if (*suffix == NUL) { break; } assert(MAXPATHL >= l); - copy_option_part(&buf, NameBuff + l, MAXPATHL - l, ","); + NameBufflen = l + copy_option_part(&suffix, NameBuff + l, MAXPATHL - l, ","); } } } @@ -1470,15 +1562,15 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch break; } - buf = xmalloc(MAXPATHL); + char *buf = xmalloc(MAXPATHL); // copy next path - buf[0] = 0; + buf[0] = NUL; copy_option_part(&dir, buf, MAXPATHL, " ,"); // get the stopdir string r_ptr = vim_findfile_stopdir(buf); - *search_ctx = vim_findfile_init(buf, *file_to_find, + *search_ctx = vim_findfile_init(buf, *file_to_find, file_to_findlen, r_ptr, 100, false, find_what, *search_ctx, false, rel_fname); if (*search_ctx != NULL) { @@ -1618,19 +1710,24 @@ char *file_name_in_line(char *line, int col, int options, int count, char *rel_f } if (file_lnum != NULL) { - const char *line_english = " line "; - const char *line_transl = _(line_msg); + const char *match_text = " line "; // english + size_t match_textlen = 6; // Get the number after the file name and a separator character. // Also accept " line 999" with and without the same translation as // used in last_set_msg(). char *p = ptr + len; - if (strncmp(p, line_english, strlen(line_english)) == 0) { - p += strlen(line_english); - } else if (strncmp(p, line_transl, strlen(line_transl)) == 0) { - p += strlen(line_transl); + if (strncmp(p, match_text, match_textlen) == 0) { + p += match_textlen; } else { - p = skipwhite(p); + // no match with english, try localized + match_text = _(line_msg); + match_textlen = strlen(match_text); + if (strncmp(p, match_text, match_textlen) == 0) { + p += match_textlen; + } else { + p = skipwhite(p); + } } if (*p != NUL) { if (!isdigit((uint8_t)(*p))) { diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 61b252f823..b79ecf22d7 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -353,10 +353,9 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } } - if (!read_buffer && !read_stdin && !read_fifo) { + if (!read_stdin && fname != NULL) { perm = os_getperm(fname); - // On Unix it is possible to read a directory, so we have to - // check for it before os_open(). + } #ifdef OPEN_CHR_FILES # define IS_CHR_DEV(perm, fname) S_ISCHR(perm) && is_dev_fd_file(fname) @@ -364,12 +363,15 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, # define IS_CHR_DEV(perm, fname) false #endif + if (!read_stdin && !read_buffer && !read_fifo) { if (perm >= 0 && !S_ISREG(perm) // not a regular file ... && !S_ISFIFO(perm) // ... or fifo && !S_ISSOCK(perm) // ... or socket && !(IS_CHR_DEV(perm, fname)) // ... or a character special file named /dev/fd/<n> ) { + // On Unix it is possible to read a directory, so we have to + // check for it before os_open(). if (S_ISDIR(perm)) { if (!silent) { filemess(curbuf, fname, _(msg_is_a_directory)); diff --git a/src/nvim/fold.c b/src/nvim/fold.c index b59933d600..45cc327444 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -2098,10 +2098,7 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot) // this in other situations, the changed lines will be redrawn anyway and // this method can cause the whole window to be updated. if (end != bot) { - if (wp->w_redraw_top == 0 || wp->w_redraw_top > top) { - wp->w_redraw_top = top; - } - wp->w_redraw_bot = MAX(wp->w_redraw_bot, end); + redraw_win_range_later(wp, top, end); } invalid_top = 0; diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index e88f5dbe70..4ed83312ef 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -1296,7 +1296,7 @@ static int ins_compl_build_pum(void) compl_shown_match = comp; } } - if (!shown_match_ok && comp == compl_shown_match && !compl_no_select) { + if (!shown_match_ok && comp == compl_shown_match) { cur = i; shown_match_ok = true; } @@ -1744,6 +1744,7 @@ void ins_compl_clear(void) compl_cont_status = 0; compl_started = false; compl_matches = 0; + compl_selected_item = -1; compl_ins_end_col = 0; API_CLEAR_STRING(compl_pattern); API_CLEAR_STRING(compl_leader); @@ -1912,7 +1913,7 @@ static void ins_compl_new_leader(void) compl_restarting = false; } - compl_enter_selects = !compl_used_match; + compl_enter_selects = !compl_used_match && compl_selected_item != -1; // Show the popup menu with a different set of matches. ins_compl_show_pum(); @@ -3780,20 +3781,20 @@ void ins_compl_delete(bool new_leader) curwin->w_cursor.col = compl_ins_end_col; } - char *remaining = NULL; + String remaining = STRING_INIT; if (curwin->w_cursor.lnum > compl_lnum) { if (curwin->w_cursor.col < get_cursor_line_len()) { - char *line = get_cursor_line_ptr(); - remaining = xstrdup(line + curwin->w_cursor.col); + remaining = cbuf_to_string(get_cursor_pos_ptr(), (size_t)get_cursor_pos_len()); } while (curwin->w_cursor.lnum > compl_lnum) { if (ml_delete(curwin->w_cursor.lnum, false) == FAIL) { - if (remaining) { - XFREE_CLEAR(remaining); + if (remaining.data) { + xfree(remaining.data); } return; } + deleted_lines_mark(curwin->w_cursor.lnum, 1); curwin->w_cursor.lnum--; } // move cursor to end of line @@ -3802,8 +3803,8 @@ void ins_compl_delete(bool new_leader) if ((int)curwin->w_cursor.col > col) { if (stop_arrow() == FAIL) { - if (remaining) { - XFREE_CLEAR(remaining); + if (remaining.data) { + xfree(remaining.data); } return; } @@ -3811,11 +3812,11 @@ void ins_compl_delete(bool new_leader) compl_ins_end_col = curwin->w_cursor.col; } - if (remaining != NULL) { + if (remaining.data != NULL) { orig_col = curwin->w_cursor.col; - ins_str(remaining); + ins_str(remaining.data, remaining.size); curwin->w_cursor.col = orig_col; - xfree(remaining); + xfree(remaining.data); } // TODO(vim): is this sufficient for redrawing? Redrawing everything diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c index a9f8c9222a..b6ba73f7c1 100644 --- a/src/nvim/keycodes.c +++ b/src/nvim/keycodes.c @@ -261,6 +261,8 @@ static const struct key_name_entry { { K_HELP, "Help" }, { K_UNDO, "Undo" }, + { K_FIND, "Find" }, // DEC key, often used as 'Home' + { K_KSELECT, "Select" }, // DEC key, often used as 'End' { K_INS, "Insert" }, { K_INS, "Ins" }, // Alternative name { K_KINS, "kInsert" }, diff --git a/src/nvim/keycodes.h b/src/nvim/keycodes.h index 5a7ddd4847..e24e30e7d0 100644 --- a/src/nvim/keycodes.h +++ b/src/nvim/keycodes.h @@ -352,6 +352,8 @@ enum key_extra { #define K_HELP TERMCAP2KEY('%', '1') #define K_UNDO TERMCAP2KEY('&', '8') +#define K_FIND TERMCAP2KEY('@', '0') // DEC key, often used as Home +#define K_KSELECT TERMCAP2KEY('*', '6') // DEC key, often used as End #define K_BS TERMCAP2KEY('k', 'b') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 3e33fcd142..259f2d4739 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -998,16 +998,21 @@ static int node_symbol(lua_State *L) static int node_field(lua_State *L) { TSNode node = node_check(L, 1); + uint32_t count = ts_node_child_count(node); + int curr_index = 0; size_t name_len; const char *field_name = luaL_checklstring(L, 2, &name_len); - lua_newtable(L); // [table] + lua_newtable(L); - TSNode field = ts_node_child_by_field_name(node, field_name, (uint32_t)name_len); - if (!ts_node_is_null(field)) { - push_node(L, field, 1); // [table, node] - lua_rawseti(L, -2, 1); + for (uint32_t i = 0; i < count; i++) { + const char *child_field_name = ts_node_field_name_for_child(node, i); + if (strequal(field_name, child_field_name)) { + TSNode child = ts_node_child(node, i); + push_node(L, child, 1); + lua_rawseti(L, -2, ++curr_index); + } } return 1; diff --git a/src/nvim/match.c b/src/nvim/match.c index 86cab5221d..a1126fbbcd 100644 --- a/src/nvim/match.c +++ b/src/nvim/match.c @@ -190,19 +190,7 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in // Calculate top and bottom lines for redrawing area if (toplnum != 0) { - if (wp->w_buffer->b_mod_set) { - if (wp->w_buffer->b_mod_top > toplnum) { - wp->w_buffer->b_mod_top = toplnum; - } - if (wp->w_buffer->b_mod_bot < botlnum) { - wp->w_buffer->b_mod_bot = botlnum; - } - } else { - wp->w_buffer->b_mod_set = true; - wp->w_buffer->b_mod_top = toplnum; - wp->w_buffer->b_mod_bot = botlnum; - wp->w_buffer->b_mod_xlines = 0; - } + redraw_win_range_later(wp, toplnum, botlnum); m->mit_toplnum = toplnum; m->mit_botlnum = botlnum; rtype = UPD_VALID; @@ -268,19 +256,7 @@ static int match_delete(win_T *wp, int id, bool perr) vim_regfree(cur->mit_match.regprog); xfree(cur->mit_pattern); if (cur->mit_toplnum != 0) { - if (wp->w_buffer->b_mod_set) { - if (wp->w_buffer->b_mod_top > cur->mit_toplnum) { - wp->w_buffer->b_mod_top = cur->mit_toplnum; - } - if (wp->w_buffer->b_mod_bot < cur->mit_botlnum) { - wp->w_buffer->b_mod_bot = cur->mit_botlnum; - } - } else { - wp->w_buffer->b_mod_set = true; - wp->w_buffer->b_mod_top = cur->mit_toplnum; - wp->w_buffer->b_mod_bot = cur->mit_botlnum; - wp->w_buffer->b_mod_xlines = 0; - } + redraw_win_range_later(wp, cur->mit_toplnum, cur->mit_botlnum); rtype = UPD_VALID; } xfree(cur->mit_pos_array); diff --git a/src/nvim/message.c b/src/nvim/message.c index 6fc102e4ff..ef4cde8a80 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -3162,6 +3162,8 @@ void msg_ext_ui_flush(void) if (!ui_has(kUIMessages)) { msg_ext_kind = NULL; return; + } else if (msg_ext_skip_flush) { + return; } msg_ext_emit_chunk(); diff --git a/src/nvim/message.h b/src/nvim/message.h index 13746406f9..55c58114f4 100644 --- a/src/nvim/message.h +++ b/src/nvim/message.h @@ -34,6 +34,8 @@ extern MessageHistoryEntry *first_msg_hist; extern MessageHistoryEntry *last_msg_hist; EXTERN bool msg_ext_need_clear INIT( = false); +// Set to true to force grouping a set of message chunks into a single `cmdline_show` event. +EXTERN bool msg_ext_skip_flush INIT( = false); /// allocated grid for messages. Used when display+=msgsep is set, or /// ext_multigrid is active. See also the description at msg_scroll_flush() diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index fa50afd83b..1d24198455 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -486,15 +486,15 @@ void rpc_close(Channel *channel) channel->rpc.closed = true; channel_decref(channel); - if (ui_client_channel_id && channel->id == ui_client_channel_id) { - assert(!channel->detach); // `Channel.detach` is not currently used by the UI client. - exit_on_closed_chan(0); - } else if (channel->streamtype == kChannelStreamStdio) { - // Avoid hanging when there are no other UIs and a prompt is triggered on exit. - remote_ui_disconnect(channel->id); + bool is_ui_client = ui_client_channel_id && channel->id == ui_client_channel_id; + if (is_ui_client || channel->streamtype == kChannelStreamStdio) { + if (!is_ui_client) { + // Avoid hanging when there are no other UIs and a prompt is triggered on exit. + remote_ui_disconnect(channel->id); + } if (!channel->detach) { - exit_on_closed_chan(0); + exit_on_closed_chan(channel->exit_status == -1 ? 0 : channel->exit_status); } } } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 2491621fbc..3e2fa13cdd 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -3770,10 +3770,16 @@ void ex_display(exarg_T *eap) } int hl_id = HLF_8; + msg_ext_set_kind("list_cmd"); + msg_ext_skip_flush = true; // Highlight title msg_puts_title(_("\nType Name Content")); for (int i = -1; i < NUM_REGISTERS && !got_int; i++) { int name = get_register_name(i); + if (arg != NULL && vim_strchr(arg, name) == NULL) { + continue; // did not ask for this register + } + switch (get_reg_type(name, NULL)) { case kMTLineWise: type = 'l'; break; @@ -3783,10 +3789,6 @@ void ex_display(exarg_T *eap) type = 'b'; break; } - if (arg != NULL && vim_strchr(arg, name) == NULL) { - continue; // did not ask for this register - } - if (i == -1) { if (y_previous != NULL) { yb = y_previous; @@ -3890,6 +3892,7 @@ void ex_display(exarg_T *eap) msg_puts("\n c \"= "); dis_msg(expr_line, false); } + msg_ext_skip_flush = false; } /// display a string for do_dis() @@ -4510,8 +4513,6 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd) /// @return true if some character was changed. bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) { - char *buf1 = NULL; - char buf2[NUMBUFLEN]; int pre; // 'X' or 'x': hex; '0': octal; 'B' or 'b': bin static bool hexupper = false; // 0xABC uvarnumber_T n; @@ -4775,7 +4776,7 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) // Prepare the leading characters in buf1[]. // When there are many leading zeros it could be very long. // Allocate a bit too much. - buf1 = xmalloc((size_t)length + NUMBUFLEN); + char *buf1 = xmalloc((size_t)length + NUMBUFLEN); ptr = buf1; if (negative && (!visual || was_positive)) { *ptr++ = '-'; @@ -4790,9 +4791,10 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) } // Put the number characters in buf2[]. + char buf2[NUMBUFLEN]; + int buf2len = 0; if (pre == 'b' || pre == 'B') { size_t bits = 0; - size_t i = 0; // leading zeros for (bits = 8 * sizeof(n); bits > 0; bits--) { @@ -4801,21 +4803,21 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) } } - while (bits > 0 && i < NUMBUFLEN - 1) { - buf2[i++] = ((n >> --bits) & 0x1) ? '1' : '0'; + while (bits > 0 && buf2len < NUMBUFLEN - 1) { + buf2[buf2len++] = ((n >> --bits) & 0x1) ? '1' : '0'; } - buf2[i] = NUL; + buf2[buf2len] = NUL; } else if (pre == 0) { - vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIu64, (uint64_t)n); + buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIu64, (uint64_t)n); } else if (pre == '0') { - vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIo64, (uint64_t)n); + buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIo64, (uint64_t)n); } else if (hexupper) { - vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIX64, (uint64_t)n); + buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIX64, (uint64_t)n); } else { - vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIx64, (uint64_t)n); + buf2len = vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIx64, (uint64_t)n); } - length -= (int)strlen(buf2); + length -= buf2len; // Adjust number of zeros to the new number of digits, so the // total length of the number remains the same. @@ -4827,8 +4829,14 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) } } *ptr = NUL; - strcat(buf1, buf2); - ins_str(buf1); // insert the new number + int buf1len = (int)(ptr - buf1); + + STRCPY(buf1 + buf1len, buf2); + buf1len += buf2len; + + ins_str(buf1, (size_t)buf1len); // insert the new number + xfree(buf1); + endpos = curwin->w_cursor; if (curwin->w_cursor.col) { curwin->w_cursor.col--; @@ -4845,7 +4853,6 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) } theend: - xfree(buf1); if (visual) { curwin->w_cursor = save_cursor; } else if (did_change) { diff --git a/src/nvim/path.c b/src/nvim/path.c index 3df77571a1..6c6a6f58c0 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -816,13 +816,13 @@ static int find_previous_pathsep(char *path, char **psep) static bool is_unique(char *maybe_unique, garray_T *gap, int i) FUNC_ATTR_NONNULL_ALL { + size_t candidate_len = strlen(maybe_unique); char **other_paths = gap->ga_data; for (int j = 0; j < gap->ga_len; j++) { if (j == i) { continue; // don't compare it with itself } - size_t candidate_len = strlen(maybe_unique); size_t other_path_len = strlen(other_paths[j]); if (other_path_len < candidate_len) { continue; // it's different when it's shorter @@ -849,9 +849,10 @@ static void expand_path_option(char *curdir, char *path_option, garray_T *gap) FUNC_ATTR_NONNULL_ALL { char *buf = xmalloc(MAXPATHL); + size_t curdirlen = 0; while (*path_option != NUL) { - copy_option_part(&path_option, buf, MAXPATHL, " ,"); + size_t buflen = copy_option_part(&path_option, buf, MAXPATHL, " ,"); if (buf[0] == '.' && (buf[1] == NUL || vim_ispathsep(buf[1]))) { // Relative to current buffer: @@ -861,34 +862,40 @@ static void expand_path_option(char *curdir, char *path_option, garray_T *gap) continue; } char *p = path_tail(curbuf->b_ffname); - size_t len = (size_t)(p - curbuf->b_ffname); - if (len + strlen(buf) >= MAXPATHL) { + size_t plen = (size_t)(p - curbuf->b_ffname); + if (plen + strlen(buf) >= MAXPATHL) { continue; } if (buf[1] == NUL) { - buf[len] = NUL; + buf[plen] = NUL; } else { - STRMOVE(buf + len, buf + 2); + memmove(buf + plen, buf + 2, (buflen - 2) + 1); // +1 for NUL } - memmove(buf, curbuf->b_ffname, len); - simplify_filename(buf); + memmove(buf, curbuf->b_ffname, plen); + buflen = simplify_filename(buf); } else if (buf[0] == NUL) { STRCPY(buf, curdir); // relative to current directory + if (curdirlen == 0) { + curdirlen = strlen(curdir); + } + buflen = curdirlen; } else if (path_with_url(buf)) { continue; // URL can't be used here } else if (!path_is_absolute(buf)) { // Expand relative path to their full path equivalent - size_t len = strlen(curdir); - if (len + strlen(buf) + 3 > MAXPATHL) { + if (curdirlen == 0) { + curdirlen = strlen(curdir); + } + if (curdirlen + buflen + 3 > MAXPATHL) { continue; } - STRMOVE(buf + len + 1, buf); + memmove(buf + curdirlen + 1, buf, buflen + 1); // +1 for NUL STRCPY(buf, curdir); - buf[len] = PATHSEP; - simplify_filename(buf); + buf[curdirlen] = PATHSEP; + buflen = simplify_filename(buf); } - GA_APPEND(char *, gap, xstrdup(buf)); + GA_APPEND(char *, gap, xmemdupz(buf, buflen)); } xfree(buf); @@ -959,7 +966,7 @@ static void uniquefy_paths(garray_T *gap, char *pattern, char *path_option) char *file_pattern = xmalloc(len + 2); file_pattern[0] = '*'; file_pattern[1] = NUL; - strcat(file_pattern, pattern); + STRCPY(file_pattern + 1, pattern); char *pat = file_pat_to_reg_pat(file_pattern, NULL, NULL, false); xfree(file_pattern); if (pat == NULL) { @@ -987,7 +994,7 @@ static void uniquefy_paths(garray_T *gap, char *pattern, char *path_option) bool is_in_curdir = path_fnamencmp(curdir, path, (size_t)(dir_end - path)) == 0 && curdir[dir_end - path] == NUL; if (is_in_curdir) { - in_curdir[i] = xstrdup(path); + in_curdir[i] = xmemdupz(path, len); } // Shorten the filename while maintaining its uniqueness @@ -1012,7 +1019,8 @@ static void uniquefy_paths(garray_T *gap, char *pattern, char *path_option) && is_unique(pathsep_p + 1, gap, i) && path_cutoff != NULL && pathsep_p + 1 >= path_cutoff) { sort_again = true; - memmove(path, pathsep_p + 1, strlen(pathsep_p)); + memmove(path, pathsep_p + 1, + (size_t)((path + len) - (pathsep_p + 1)) + 1); // +1 for NUL break; } } @@ -1031,9 +1039,7 @@ static void uniquefy_paths(garray_T *gap, char *pattern, char *path_option) // c:\file.txt c:\ .\file.txt short_name = path_shorten_fname(path, curdir); if (short_name != NULL && short_name > path + 1) { - STRCPY(path, "."); - add_pathsep(path); - STRMOVE(path + strlen(path), short_name); + vim_snprintf(path, MAXPATHL, ".%s%s", PATHSEPSTR, short_name); } } os_breakcheck(); @@ -1041,7 +1047,6 @@ static void uniquefy_paths(garray_T *gap, char *pattern, char *path_option) // Shorten filenames in /in/current/directory/{filename} for (int i = 0; i < gap->ga_len && !got_int; i++) { - char *rel_path; char *path = in_curdir[i]; if (path == NULL) { @@ -1059,10 +1064,10 @@ static void uniquefy_paths(garray_T *gap, char *pattern, char *path_option) continue; } - rel_path = xmalloc(strlen(short_name) + strlen(PATHSEPSTR) + 2); - STRCPY(rel_path, "."); - add_pathsep(rel_path); - strcat(rel_path, short_name); + size_t rel_pathsize = 1 + STRLEN_LITERAL(PATHSEPSTR) + strlen(short_name) + 1; + char *rel_path = xmalloc(rel_pathsize); + + vim_snprintf(rel_path, rel_pathsize, ".%s%s", PATHSEPSTR, short_name); xfree(fnames[i]); fnames[i] = rel_path; @@ -1518,7 +1523,7 @@ void addfile(garray_T *gap, char *f, int flags) // its simplest form by stripping out unneeded components, if any. The // resulting file name is simplified in place and will either be the same // length as that supplied, or shorter. -void simplify_filename(char *filename) +size_t simplify_filename(char *filename) FUNC_ATTR_NONNULL_ALL { int components = 0; @@ -1539,10 +1544,12 @@ void simplify_filename(char *filename) } while (vim_ispathsep(*p)); } char *start = p; // remember start after "c:/" or "/" or "///" + char *p_end = p + strlen(p); // point to NUL at end of string "p" #ifdef UNIX // Posix says that "//path" is unchanged but "///path" is "/path". if (start > filename + 2) { - STRMOVE(filename + 1, p); + memmove(filename + 1, p, (size_t)(p_end - p) + 1); // +1 for NUL + p_end -= (size_t)(p - (filename + 1)); start = p = filename + 1; } #endif @@ -1551,7 +1558,8 @@ void simplify_filename(char *filename) // At this point "p" is pointing to the char following a single "/" // or "p" is at the "start" of the (absolute or relative) path name. if (vim_ispathsep(*p)) { - STRMOVE(p, p + 1); // remove duplicate "/" + memmove(p, p + 1, (size_t)(p_end - (p + 1)) + 1); // remove duplicate "/" + p_end--; } else if (p[0] == '.' && (vim_ispathsep(p[1]) || p[1] == NUL)) { if (p == start && relative) { @@ -1569,7 +1577,8 @@ void simplify_filename(char *filename) } else if (p > start) { p--; // strip preceding path separator } - STRMOVE(p, tail); + memmove(p, tail, (size_t)(p_end - tail) + 1); + p_end -= (size_t)(tail - p); } } else if (p[0] == '.' && p[1] == '.' && (vim_ispathsep(p[2]) || p[2] == NUL)) { @@ -1667,16 +1676,19 @@ void simplify_filename(char *filename) if (p > start && tail[-1] == '.') { p--; } - STRMOVE(p, tail); // strip previous component + memmove(p, tail, (size_t)(p_end - tail) + 1); // strip previous component + p_end -= (size_t)(tail - p); } components--; } - } else if (p == start && !relative) { // leading "/.." or "/../" - STRMOVE(p, tail); // strip ".." or "../" + } else if (p == start && !relative) { // leading "/.." or "/../" + memmove(p, tail, (size_t)(p_end - tail) + 1); // strip ".." or "../" + p_end -= (size_t)(tail - p); } else { - if (p == start + 2 && p[-2] == '.') { // leading "./../" - STRMOVE(p - 2, p); // strip leading "./" + if (p == start + 2 && p[-2] == '.') { // leading "./../" + memmove(p - 2, p, (size_t)(p_end - p) + 1); // strip leading "./" + p_end -= 2; tail -= 2; } p = tail; // skip to char after ".." or "../" @@ -1686,6 +1698,8 @@ void simplify_filename(char *filename) p = (char *)path_next_component(p); } } while (*p != NUL); + + return (size_t)(p_end - filename); } /// Checks for a Windows drive letter ("C:/") at the start of the path. diff --git a/src/nvim/search.c b/src/nvim/search.c index 9f8ceae2a0..04f33b9445 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -2704,6 +2704,7 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst static int last_maxcount = SEARCH_STAT_DEF_MAX_COUNT; static int chgtick = 0; static char *lastpat = NULL; + static size_t lastpatlen = 0; static buf_T *lbuf = NULL; CLEAR_POINTER(stat); @@ -2725,9 +2726,9 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst // Unfortunately, there is no STRNICMP function. // XXX: above comment should be "no MB_STRCMP function" ? if (!(chgtick == buf_get_changedtick(curbuf) - && lastpat != NULL // suppress clang/NULL passed as nonnull parameter - && STRNICMP(lastpat, spats[last_idx].pat, strlen(lastpat)) == 0 - && strlen(lastpat) == strlen(spats[last_idx].pat) + && (lastpat != NULL // suppress clang/NULL passed as nonnull parameter + && mb_strnicmp(lastpat, spats[last_idx].pat, lastpatlen) == 0 + && lastpatlen == spats[last_idx].patlen) && equalpos(lastpos, *cursor_pos) && lbuf == curbuf) || wraparound || cur < 0 || (maxcount > 0 && cur > maxcount) @@ -2780,7 +2781,8 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst } if (done_search) { xfree(lastpat); - lastpat = xstrdup(spats[last_idx].pat); + lastpat = xstrnsave(spats[last_idx].pat, spats[last_idx].patlen); + lastpatlen = spats[last_idx].patlen; chgtick = (int)buf_get_changedtick(curbuf); lbuf = curbuf; lastpos = p; diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c index 21bfa367bf..ca47d9ea3c 100644 --- a/src/nvim/spellsuggest.c +++ b/src/nvim/spellsuggest.c @@ -516,7 +516,7 @@ void spell_suggest(int count) spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit, true, need_cap, true); - msg_ext_set_kind("list_cmd"); + msg_ext_set_kind("confirm"); if (GA_EMPTY(&sug.su_ga)) { msg(_("Sorry, no suggestions"), 0); } else if (count > 0) { diff --git a/src/nvim/tag.c b/src/nvim/tag.c index c676b00986..557d41a467 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -813,6 +813,7 @@ static void print_tag_list(bool new_tag, bool use_tagstack, int num_matches, cha if (msg_col == 0) { msg_didout = false; // overwrite previous message } + msg_ext_set_kind("confirm"); msg_start(); msg_puts_hl(_(" # pri kind tag"), HLF_T, false); msg_clr_eos(); @@ -2556,7 +2557,7 @@ int get_tagfname(tagname_T *tnp, int first, char *buf) STRMOVE(filename + 1, filename); *filename++ = NUL; - tnp->tn_search_ctx = vim_findfile_init(buf, filename, + tnp->tn_search_ctx = vim_findfile_init(buf, filename, strlen(filename), r_ptr, 100, false, // don't free visited list FINDFILE_FILE, // we search for a file diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 61b55f71de..47630ddea9 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -662,6 +662,10 @@ bool terminal_enter(void) State = MODE_TERMINAL; mapped_ctrl_c |= MODE_TERMINAL; // Always map CTRL-C to avoid interrupt. RedrawingDisabled = false; + if (!s->term->cursor.visible) { + // Hide cursor if it should be hidden. Do so right after setting State, before events. + ui_busy_start(); + } // Disable these options in terminal-mode. They are nonsense because cursor is // placed at end of buffer to "follow" output. #11072 @@ -693,14 +697,10 @@ bool terminal_enter(void) refresh_cursor(s->term); adjust_topline(s->term, buf, 0); // scroll to end + showmode(); curwin->w_redr_status = true; // For mode() in statusline. #8323 redraw_custom_title_later(); - if (!s->term->cursor.visible) { - // Hide cursor if it should be hidden - ui_busy_start(); - } ui_cursor_shape(); - showmode(); apply_autocmds(EVENT_TERMENTER, NULL, NULL, false, curbuf); may_trigger_modechanged(); @@ -716,6 +716,11 @@ bool terminal_enter(void) } State = save_state; RedrawingDisabled = s->save_rd; + if (!s->term->cursor.visible) { + // If cursor was hidden, show it again. Do so right after restoring State, before events. + ui_busy_stop(); + } + apply_autocmds(EVENT_TERMLEAVE, NULL, NULL, false, curbuf); // Restore the terminal cursor to what is set in 'guicursor' @@ -746,10 +751,6 @@ bool terminal_enter(void) } else { unshowmode(true); } - if (!s->term->cursor.visible) { - // If cursor was hidden, show it again - ui_busy_stop(); - } ui_cursor_shape(); if (s->close) { bool wipe = s->term->buf_handle != 0; diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c index ca2829fecb..ffa273c0ad 100644 --- a/src/nvim/textformat.c +++ b/src/nvim/textformat.c @@ -413,7 +413,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on // add the additional whitespace needed after the // comment leader for the numbered list. for (int i = 0; i < padding; i++) { - ins_str(" "); + ins_str(S_LEN(" ")); } } else { set_indent(second_indent, SIN_CHANGED); diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 8a4aea1efe..fd392d479d 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -124,6 +124,10 @@ describe('API/extmarks', function() ) eq("Invalid 'hl_mode': 'foo'", pcall_err(set_extmark, ns, marks[2], 0, 0, { hl_mode = 'foo' })) eq( + "Invalid 'virt_lines_overflow': 'foo'", + pcall_err(set_extmark, ns, marks[2], 0, 0, { virt_lines_overflow = 'foo' }) + ) + eq( "Invalid 'id': expected Integer, got Array", pcall_err(set_extmark, ns, {}, 0, 0, { end_col = 1, end_row = 1 }) ) @@ -1576,11 +1580,6 @@ describe('API/extmarks', function() virt_text_hide = true, virt_text_pos = 'right_align', }) - set_extmark(ns, marks[2], 0, 0, { - priority = 0, - virt_text = { { '', 'Macro' }, { '', { 'Type', 'Search' } }, { '' } }, - virt_text_win_col = 1, - }) eq({ 0, 0, @@ -1607,12 +1606,20 @@ describe('API/extmarks', function() }, virt_lines_above = true, virt_lines_leftcol = true, + virt_lines_overflow = 'trunc', virt_text = { { 'text', 'Macro' }, { '???' }, { 'stack', { 'Type', 'Search' } } }, virt_text_repeat_linebreak = false, virt_text_hide = true, virt_text_pos = 'right_align', }, }, get_extmark_by_id(ns, marks[1], { details = true })) + + set_extmark(ns, marks[2], 0, 0, { + priority = 0, + virt_text = { { '', 'Macro' }, { '', { 'Type', 'Search' } }, { '' } }, + virt_text_repeat_linebreak = true, + virt_text_win_col = 1, + }) eq({ 0, 0, @@ -1621,13 +1628,35 @@ describe('API/extmarks', function() right_gravity = true, priority = 0, virt_text = { { '', 'Macro' }, { '', { 'Type', 'Search' } }, { '' } }, - virt_text_repeat_linebreak = false, + virt_text_repeat_linebreak = true, virt_text_hide = false, virt_text_pos = 'win_col', virt_text_win_col = 1, }, }, get_extmark_by_id(ns, marks[2], { details = true })) - set_extmark(ns, marks[3], 0, 0, { cursorline_hl_group = 'Statement' }) + + set_extmark(ns, marks[3], 0, 0, { + priority = 0, + ui_watched = true, + virt_lines = { { { '', 'Macro' }, { '' }, { '', '' } } }, + virt_lines_overflow = 'scroll', + }) + eq({ + 0, + 0, + { + ns_id = 1, + right_gravity = true, + ui_watched = true, + priority = 0, + virt_lines = { { { '', 'Macro' }, { '' }, { '', '' } } }, + virt_lines_above = false, + virt_lines_leftcol = false, + virt_lines_overflow = 'scroll', + }, + }, get_extmark_by_id(ns, marks[3], { details = true })) + + set_extmark(ns, marks[4], 0, 0, { cursorline_hl_group = 'Statement' }) eq({ 0, 0, @@ -1637,8 +1666,9 @@ describe('API/extmarks', function() priority = 4096, right_gravity = true, }, - }, get_extmark_by_id(ns, marks[3], { details = true })) - set_extmark(ns, marks[4], 0, 0, { + }, get_extmark_by_id(ns, marks[4], { details = true })) + + set_extmark(ns, marks[5], 0, 0, { end_col = 1, conceal = 'a', spell = true, @@ -1655,8 +1685,9 @@ describe('API/extmarks', function() right_gravity = true, spell = true, }, - }, get_extmark_by_id(ns, marks[4], { details = true })) - set_extmark(ns, marks[5], 0, 0, { + }, get_extmark_by_id(ns, marks[5], { details = true })) + + set_extmark(ns, marks[6], 0, 0, { end_col = 1, spell = false, }) @@ -1671,7 +1702,8 @@ describe('API/extmarks', function() right_gravity = true, spell = false, }, - }, get_extmark_by_id(ns, marks[5], { details = true })) + }, get_extmark_by_id(ns, marks[6], { details = true })) + api.nvim_buf_clear_namespace(0, ns, 0, -1) -- legacy sign mark includes sign name command('sign define sign1 text=s1 texthl=Title linehl=LineNR numhl=Normal culhl=CursorLine') diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua index 7c68de272b..39072ede9c 100644 --- a/test/functional/editor/completion_spec.lua +++ b/test/functional/editor/completion_spec.lua @@ -460,7 +460,7 @@ describe('completion', function() June]]) end) - it('Enter does not select original text', function() + it('Enter inserts newline at original text after adding leader', function() feed('iJ<C-x><C-u>') poke_eventloop() feed('u') @@ -506,19 +506,23 @@ describe('completion', function() ]]) end) - it('Enter selects original text after adding leader', function() + it('Enter inserts newline at original text after adding leader', function() feed('iJ<C-x><C-u>') poke_eventloop() feed('u') poke_eventloop() feed('<CR>') - expect('Ju') + expect([[ + Ju + ]]) feed('<Esc>') poke_eventloop() -- The behavior should be the same when completion has been interrupted, -- which can happen interactively if the completion function is slow. - feed('SJ<C-x><C-u>u<CR>') - expect('Ju') + feed('ggVGSJ<C-x><C-u>u<CR>') + expect([[ + Ju + ]]) end) end) diff --git a/test/functional/editor/defaults_spec.lua b/test/functional/editor/defaults_spec.lua index 25332d5b1f..876810ce6f 100644 --- a/test/functional/editor/defaults_spec.lua +++ b/test/functional/editor/defaults_spec.lua @@ -167,7 +167,7 @@ describe('default', function() end) describe('unimpaired-style mappings', function() - it('show the command ouptut when successful', function() + it('show the command output when successful', function() n.clear({ args_rm = { '--cmd' } }) local screen = Screen.new(40, 8) n.fn.setqflist({ diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index 2974564f70..848db7d088 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -150,6 +150,58 @@ describe('swapfile detection', function() rmdir(swapdir) end) + it('redrawing during prompt does not break treesitter', function() + local testfile = 'Xtest_swapredraw.lua' + write_file( + testfile, + [[ +vim.o.foldmethod = 'expr' +vim.o.foldexpr = 'v:lua.vim.treesitter.foldexpr()' +vim.defer_fn(function() + vim.api.nvim__redraw({ valid = false }) +end, 500) +pcall(vim.cmd.edit, 'Xtest_swapredraw.lua') + ]] + ) + exec(init) + command('edit! ' .. testfile) + command('preserve') + local nvim2 = n.new_session(true, { args = { '--clean', '--embed' }, merge = false }) + set_session(nvim2) + local screen2 = Screen.new(100, 40) + screen2:add_extra_attr_ids({ + [100] = { foreground = Screen.colors.NvimLightGrey2 }, + [101] = { foreground = Screen.colors.NvimLightGreen }, + [102] = { + foreground = Screen.colors.NvimLightGrey4, + background = Screen.colors.NvimDarkGrey1, + }, + [104] = { foreground = Screen.colors.NvimLightCyan }, + [105] = { foreground = Screen.colors.NvimDarkGrey4 }, + [106] = { + foreground = Screen.colors.NvimDarkGrey3, + background = Screen.colors.NvimLightGrey3, + }, + }) + exec(init) + command('autocmd! nvim.swapfile') -- Delete the default handler (which skips the dialog). + feed(':edit ' .. testfile .. '<CR>') + feed('E:source<CR>') + screen2:sleep(1000) + feed('E') + screen2:expect([[ + {100:^vim.o.foldmethod} {100:=} {101:'expr'} | + {100:vim.o.foldexpr} {100:=} {101:'v:lua.vim.treesitter.foldexpr()'} | + {102:+-- 3 lines: vim.defer_fn(function()·······························································}| + {104:pcall}{100:(vim.cmd.edit,} {101:'Xtest_swapredraw.lua'}{100:)} | + | + {105:~ }|*33 + {106:Xtest_swapredraw.lua 1,1 All}| + | + ]]) + nvim2:close() + end) + it('always show swapfile dialog #8840 #9027', function() local testfile = 'Xtest_swapdialog_file1' diff --git a/test/functional/legacy/edit_spec.lua b/test/functional/legacy/edit_spec.lua index d2ce80efda..1c165e0335 100644 --- a/test/functional/legacy/edit_spec.lua +++ b/test/functional/legacy/edit_spec.lua @@ -92,4 +92,28 @@ describe('edit', function() | ]]) end) + + -- oldtest: Test_edit_CAR() + it('Enter inserts newline with pum at original text after adding leader', function() + local screen = Screen.new(10, 6) + command('set cot=menu,menuone,noselect') + feed('Shello hero<CR>h<C-X><C-N>e') + screen:expect([[ + hello hero | + he^ | + {4:hello }| + {4:hero }| + {1:~ }| + {5:--} | + ]]) + + feed('<CR>') + screen:expect([[ + hello hero | + he | + ^ | + {1:~ }|*2 + {5:-- INSERT --}| + ]]) + end) end) diff --git a/test/functional/legacy/number_spec.lua b/test/functional/legacy/number_spec.lua index 4636cd0369..3212524f9d 100644 --- a/test/functional/legacy/number_spec.lua +++ b/test/functional/legacy/number_spec.lua @@ -221,30 +221,25 @@ describe("'number' and 'relativenumber'", function() func Func(timer) call cursor(1, 1) endfunc - - call timer_start(300, 'Func') ]]) - screen:expect({ - grid = [[ + screen:expect([[ {8: 3 }aaaaa | {8: 2 }bbbbb | {8: 1 }ccccc | {8: 0 }^ddddd | {1:~ }|*3 | - ]], - timeout = 100, - }) - screen:expect({ - grid = [[ + ]]) + command([[call timer_start(300, 'Func')]]) + screen:expect_unchanged(false, 100) + screen:expect([[ {8: 0 }^aaaaa | {8: 1 }bbbbb | {8: 2 }ccccc | {8: 3 }ddddd | {1:~ }|*3 | - ]], - }) + ]]) end) -- oldtest: Test_number_insert_delete_lines() diff --git a/test/functional/lua/hl_spec.lua b/test/functional/lua/hl_spec.lua index 512f6be48f..27629cf214 100644 --- a/test/functional/lua/hl_spec.lua +++ b/test/functional/lua/hl_spec.lua @@ -27,6 +27,14 @@ describe('vim.hl.range', function() '口口=口口', 'zxcvbnm', }) + screen:expect([[ + ^asdfghjkl{1:$} | + «口=口»{1:$} | + qwertyuiop{1:$} | + 口口=口口{1:$} | + zxcvbnm{1:$} | + | + ]]) end) it('works with charwise selection', function() @@ -106,7 +114,7 @@ describe('vim.hl.range', function() end) it('removes highlight after given `timeout`', function() - local timeout = 100 + local timeout = 300 exec_lua(function() local ns = vim.api.nvim_create_namespace('') vim.hl.range(0, ns, 'Search', { 0, 0 }, { 4, 0 }, { timeout = timeout }) @@ -120,7 +128,7 @@ describe('vim.hl.range', function() zxcvbnm{1:$} | | ]], - timeout = timeout, + timeout = timeout / 3, }) screen:expect([[ ^asdfghjkl{1:$} | @@ -176,6 +184,10 @@ describe('vim.hl.on_yank', function() eq({ win }, api.nvim__ns_get(ns).wins) command('wincmd w') eq({ win }, api.nvim__ns_get(ns).wins) + -- Use a new vim.hl.range() call to cancel the previous timer + exec_lua(function() + vim.hl.range(0, ns, 'Search', { 0, 0 }, { 0, 0 }, { timeout = 0 }) + end) end) it('removes old highlight if new one is created before old one times out', function() @@ -197,5 +209,9 @@ describe('vim.hl.on_yank', function() eq({ win }, api.nvim__ns_get(ns).wins) command('wincmd w') eq({ win }, api.nvim__ns_get(ns).wins) + -- Use a new vim.hl.range() call to cancel the previous timer + exec_lua(function() + vim.hl.range(0, ns, 'Search', { 0, 0 }, { 0, 0 }, { timeout = 0 }) + end) end) end) diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index d101e68273..c5fa411efe 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -562,6 +562,13 @@ describe('vim.lsp.completion: item conversion', function() range = range0, }, }, + -- luals for snippet + { + insertText = 'for ${1:index}, ${2:value} in ipairs(${3:t}) do\n\t$0\nend', + insertTextFormat = 2, + kind = 15, + label = 'for .. ipairs', + }, } local expected = { { @@ -569,6 +576,10 @@ describe('vim.lsp.completion: item conversion', function() word = 'copyOf', }, { + abbr = 'for .. ipairs', + word = 'for .. ipairs', + }, + { abbr = 'insert', word = 'insert', }, diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index a0064f741e..17e3fbbf70 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -3528,6 +3528,20 @@ describe('LSP', function() end) ) end) + + it('considers title when computing width', function() + eq( + { 17, 2 }, + exec_lua(function() + return { + vim.lsp.util._make_floating_popup_size( + { 'foo', 'bar' }, + { title = 'A very long title' } + ), + } + end) + ) + end) end) describe('lsp.util.trim.trim_empty_lines', function() diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index a824893ce3..8ebad14f15 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -382,6 +382,31 @@ describe(':terminal cursor', function() ^ | |*5 ]]) + + feed('i') + screen:expect([[ + tty ready | + |*5 + {3:-- TERMINAL --} | + ]]) + + -- Cursor currently hidden; request to show it while in a TermLeave autocmd. + -- Process events (via :sleep) to handle the escape sequence immediately. + command([[autocmd TermLeave * ++once call chansend(b:terminal_job_id, "\e[?25h") | sleep 1m]]) + feed([[<C-\><C-N>]]) -- Exit terminal mode + screen:expect([[ + tty ready | + ^ | + |*5 + ]]) + + feed('i') + screen:expect([[ + tty ready | + ^ | + |*4 + {3:-- TERMINAL --} | + ]]) end) end) diff --git a/test/functional/treesitter/fold_spec.lua b/test/functional/treesitter/fold_spec.lua index ac58df4bba..1212212d62 100644 --- a/test/functional/treesitter/fold_spec.lua +++ b/test/functional/treesitter/fold_spec.lua @@ -811,17 +811,19 @@ t2]]) ]] -- foldexpr will return '0' for all lines - local levels = get_fold_levels() ---@type integer[] - eq(19, #levels) - for lnum, level in ipairs(levels) do - eq('0', level, string.format("foldlevel[%d] == %s; expected '0'", lnum, level)) + local function expect_no_folds() + local levels = get_fold_levels() ---@type integer[] + eq(19, #levels) + for lnum, level in ipairs(levels) do + eq('0', level, string.format("foldlevel[%d] == %s; expected '0'", lnum, level)) + end end + expect_no_folds() -- reload buffer as c filetype to simulate new parser being found feed('GA// vim: ft=c<Esc>') command([[write | edit]]) - - eq({ + local foldlevels = { [1] = '>1', [2] = '1', [3] = '1', @@ -841,6 +843,14 @@ t2]]) [17] = '3', [18] = '2', [19] = '1', - }, get_fold_levels()) + } + eq(foldlevels, get_fold_levels()) + + -- only changing filetype should change the parser again + command('set ft=some_filetype_without_treesitter_parser') + expect_no_folds() + + command('set ft=c') + eq(foldlevels, get_fold_levels()) end) end) diff --git a/test/functional/treesitter/node_spec.lua b/test/functional/treesitter/node_spec.lua index 235bf7861c..2a1fa497af 100644 --- a/test/functional/treesitter/node_spec.lua +++ b/test/functional/treesitter/node_spec.lua @@ -186,4 +186,23 @@ describe('treesitter node API', function() eq(lua_eval('value:type()'), lua_eval('declarator:child_with_descendant(value):type()')) eq(vim.NIL, lua_eval('value:child_with_descendant(value)')) end) + + it('gets all children with a given field name', function() + insert([[ + function foo(a,b,c) + end + ]]) + + exec_lua(function() + local tree = vim.treesitter.get_parser(0, 'lua'):parse()[1] + _G.parameters_node = assert(tree:root():named_descendant_for_range(0, 18, 0, 18)) + _G.children_by_field = _G.parameters_node:field('name') + end) + + eq('parameters', lua_eval('parameters_node:type()')) + eq(3, lua_eval('#children_by_field')) + eq('a', lua_eval('vim.treesitter.get_node_text(children_by_field[1], 0)')) + eq('b', lua_eval('vim.treesitter.get_node_text(children_by_field[2], 0)')) + eq('c', lua_eval('vim.treesitter.get_node_text(children_by_field[3], 0)')) + end) end) diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index eb4651a81d..ebf9f5a11b 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -633,7 +633,7 @@ int x = INT_MAX; }, get_ranges()) n.feed('7ggI//<esc>') - exec_lua([[parser:parse({5, 6})]]) + exec_lua([[parser:parse(true)]]) eq('table', exec_lua('return type(parser:children().c)')) eq(2, exec_lua('return #parser:children().c:trees()')) eq({ @@ -1122,7 +1122,7 @@ print() ) eq( - 2, + 1, exec_lua(function() _G.parser:parse({ 2, 6 }) return #_G.parser:children().lua:trees() @@ -1172,10 +1172,10 @@ print() eq(true, exec_lua('return vim.treesitter.get_parser():is_valid()')) end) - it('is fully valid after a parsing a range on parsed tree', function() + it('is valid within a range on parsed tree after parsing it', function() exec_lua('vim.treesitter.get_parser():parse({5, 7})') eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)')) - eq(true, exec_lua('return vim.treesitter.get_parser():is_valid()')) + eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(nil, {5, 7})')) end) describe('when adding content with injections', function() @@ -1200,14 +1200,11 @@ print() eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()')) end) - it( - 'is fully valid after a range parse that leads to parsing not parsed injections', - function() - exec_lua('vim.treesitter.get_parser():parse({5, 7})') - eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)')) - eq(true, exec_lua('return vim.treesitter.get_parser():is_valid()')) - end - ) + it('is valid within a range on parsed tree after parsing it', function() + exec_lua('vim.treesitter.get_parser():parse({5, 7})') + eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)')) + eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(nil, {5, 7})')) + end) it( 'is valid excluding, invalid including children after a range parse that does not lead to parsing not parsed injections', @@ -1249,10 +1246,10 @@ print() eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()')) end) - it('is fully valid after a range parse that leads to parsing modified child tree', function() + it('is valid within a range parse that leads to parsing modified child tree', function() exec_lua('vim.treesitter.get_parser():parse({5, 7})') eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)')) - eq(true, exec_lua('return vim.treesitter.get_parser():is_valid()')) + eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(nil, {5, 7})')) end) it( diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 2f83e7b97a..99e6b5f78f 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -3368,58 +3368,89 @@ describe('decorations: inline virtual text', function() command("set nowrap") api.nvim_buf_set_extmark(0, ns, 0, 2, { virt_text = { { string.rep('X', 55), 'Special' } }, virt_text_pos = 'inline' }) feed('$') - screen:expect{grid=[[ + screen:expect([[ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}cdefgh^i| {1:~ }| | - ]]} + ]]) + command('set list listchars+=precedes:!') + screen:expect([[ + {1:!}{10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}cdefgh^i| + {1:~ }| + | + ]]) end) it('draws correctly with no wrap and multibyte virtual text', function() insert('12345678') command('set nowrap') api.nvim_buf_set_extmark(0, ns, 0, 2, { - virt_text = { { 'αβ̳γ̲口=', 'Special' }, { '❤️', 'Special' } }, + hl_mode = 'replace', + virt_text = { { 'α口β̳γ̲=', 'Special' }, { '❤️', 'Special' } }, virt_text_pos = 'inline', }) screen:expect([[ - 12{10:αβ̳γ̲口=❤️}34567^8 | + 12{10:α口β̳γ̲=❤️}34567^8 | {1:~ }| | ]]) feed('zl') screen:expect([[ - 2{10:αβ̳γ̲口=❤️}34567^8 | + 2{10:α口β̳γ̲=❤️}34567^8 | {1:~ }| | ]]) feed('zl') screen:expect([[ - {10:αβ̳γ̲口=❤️}34567^8 | + {10:α口β̳γ̲=❤️}34567^8 | {1:~ }| | ]]) feed('zl') screen:expect([[ - {10:β̳γ̲口=❤️}34567^8 | + {10:口β̳γ̲=❤️}34567^8 | {1:~ }| | ]]) + feed('V') + screen:expect([[ + {10:口β̳γ̲=❤️}{7:34567}^8 | + {1:~ }| + {8:-- VISUAL LINE --} | + ]]) + command('set list listchars+=precedes:!') + screen:expect([[ + {1:!<}{10:β̳γ̲=❤️}{7:34567}^8 | + {1:~ }| + {8:-- VISUAL LINE --} | + ]]) feed('zl') screen:expect([[ - {10:γ̲口=❤️}34567^8 | + {1:!}{10:β̳γ̲=❤️}{7:34567}^8 | + {1:~ }| + {8:-- VISUAL LINE --} | + ]]) + command('set nolist') + screen:expect([[ + {1:<}{10:β̳γ̲=❤️}{7:34567}^8 | + {1:~ }| + {8:-- VISUAL LINE --} | + ]]) + feed('<Esc>') + screen:expect([[ + {1:<}{10:β̳γ̲=❤️}34567^8 | {1:~ }| | ]]) feed('zl') screen:expect([[ - {10:口=❤️}34567^8 | + {10:β̳γ̲=❤️}34567^8 | {1:~ }| | ]]) feed('zl') screen:expect([[ - {10: =❤️}34567^8 | + {10:γ̲=❤️}34567^8 | {1:~ }| | ]]) @@ -3435,9 +3466,21 @@ describe('decorations: inline virtual text', function() {1:~ }| | ]]) + command('set list') + screen:expect([[ + {1:!<}34567^8 | + {1:~ }| + | + ]]) feed('zl') screen:expect([[ - {10: }34567^8 | + {1:!}34567^8 | + {1:~ }| + | + ]]) + command('set nolist') + screen:expect([[ + {1:<}34567^8 | {1:~ }| | ]]) @@ -4949,7 +4992,6 @@ if (h->n_buckets < new_n_buckets) { // expand ]]} end) - it('works with hard TABs', function() insert(example_text2) feed 'gg' @@ -5020,6 +5062,140 @@ if (h->n_buckets < new_n_buckets) { // expand ]]} end) + it('scrolls horizontally with virt_lines_overflow = "scroll" #31000', function() + command('set nowrap signcolumn=yes') + insert('abcdefghijklmnopqrstuvwxyz') + api.nvim_buf_set_extmark(0, ns, 0, 0, { + virt_lines = { + { { '12α口β̳γ̲=', 'Special' }, { '❤️345678', 'Special' } }, + { { '123\t45\t678', 'NonText' } }, + }, + virt_lines_overflow = 'scroll', + }) + screen:expect([[ + {7: }abcdefghijklmnopqrstuvwxy^z | + {7: }{16:12α口β̳γ̲=❤️345678} | + {7: }{1:123 45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }bcdefghijklmnopqrstuvwxy^z | + {7: }{16:2α口β̳γ̲=❤️345678} | + {7: }{1:23 45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }cdefghijklmnopqrstuvwxy^z | + {7: }{16:α口β̳γ̲=❤️345678} | + {7: }{1:3 45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }defghijklmnopqrstuvwxy^z | + {7: }{16:口β̳γ̲=❤️345678} | + {7: }{1: 45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }efghijklmnopqrstuvwxy^z | + {7: }{16: β̳γ̲=❤️345678} | + {7: }{1: 45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }fghijklmnopqrstuvwxy^z | + {7: }{16:β̳γ̲=❤️345678} | + {7: }{1: 45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }ghijklmnopqrstuvwxy^z | + {7: }{16:γ̲=❤️345678} | + {7: }{1: 45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }hijklmnopqrstuvwxy^z | + {7: }{16:=❤️345678} | + {7: }{1: 45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }ijklmnopqrstuvwxy^z | + {7: }{16:❤️345678} | + {7: }{1:45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }jklmnopqrstuvwxy^z | + {7: }{16: 345678} | + {7: }{1:5 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }klmnopqrstuvwxy^z | + {7: }{16:345678} | + {7: }{1: 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }lmnopqrstuvwxy^z | + {7: }{16:45678} | + {7: }{1: 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }mnopqrstuvwxy^z | + {7: }{16:5678} | + {7: }{1: 678} | + {1:~ }|*8 + | + ]]) + api.nvim_buf_set_extmark(0, ns, 0, 1, { + virt_lines = { { { '123\t45\t67', 'NonText' } } }, + virt_lines_leftcol = true, + virt_lines_overflow = 'trunc', + }) + api.nvim_buf_set_extmark(0, ns, 0, 2, { + virt_lines = { { { '123\t45\t6', 'NonText' } } }, + virt_lines_leftcol = false, + virt_lines_overflow = 'trunc', + }) + screen:expect([[ + {7: }mnopqrstuvwxy^z | + {7: }{16:5678} | + {7: }{1: 678} | + {1:123 45 67} | + {7: }{1:123 45 6} | + {1:~ }|*6 + | + ]]) + end) + it('does not show twice if end_row or end_col is specified #18622', function() screen:try_resize(50, 8) insert([[ diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index 0f4696f3d3..0cd2204c06 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -1159,30 +1159,25 @@ describe('CursorLine and CursorLineNr highlights', function() func Func(timer) call cursor(2, 1) endfunc - - call timer_start(300, 'Func') ]]) - screen:expect({ - grid = [[ + screen:expect([[ aaaaa | bbbbb | ccccc | {21:^ddddd }| {1:~ }|*3 | - ]], - timeout = 100, - }) - screen:expect({ - grid = [[ + ]]) + command([[call timer_start(300, 'Func')]]) + screen:expect_unchanged(false, 100) + screen:expect([[ aaaaa | {21:^bbbbb }| ccccc | ddddd | {1:~ }|*3 | - ]], - }) + ]]) end) it('with split windows in diff mode', function() @@ -1337,30 +1332,25 @@ describe('CursorColumn highlight', function() func Func(timer) call cursor(1, 1) endfunc - - call timer_start(300, 'Func') ]]) - screen:expect({ - grid = [[ + screen:expect([[ aaaa{21:a} | bbbb{21:b} | cccc{21:c} | dddd^d | {1:~ }|*3 | - ]], - timeout = 100, - }) - screen:expect({ - grid = [[ + ]]) + command([[call timer_start(300, 'Func')]]) + screen:expect_unchanged(false, 100) + screen:expect([[ ^aaaaa | {21:b}bbbb | {21:c}cccc | {21:d}dddd | {1:~ }|*3 | - ]], - }) + ]]) end) it('is not shown on current line with virtualedit', function() diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 98312c42c9..bc1f3480e1 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -62,6 +62,8 @@ describe('mappings', function() add_mapping('<kenter>', '<kenter>') add_mapping('<kcomma>', '<kcomma>') add_mapping('<kequal>', '<kequal>') + add_mapping('<find>', '<find>') + add_mapping('<select>', '<select>') add_mapping('<f38>', '<f38>') add_mapping('<f63>', '<f63>') end) @@ -130,6 +132,8 @@ describe('mappings', function() check_mapping('<KPComma>', '<kcomma>') check_mapping('<kequal>', '<kequal>') check_mapping('<KPEquals>', '<kequal>') + check_mapping('<Find>', '<find>') + check_mapping('<Select>', '<select>') check_mapping('<f38>', '<f38>') check_mapping('<f63>', '<f63>') end) diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 5c55dfe910..83abf77ae6 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -33,6 +33,7 @@ describe('ui/ext_messages', function() screen = Screen.new(25, 5, { rgb = true, ext_messages = true, ext_popupmenu = true }) screen:add_extra_attr_ids { [100] = { undercurl = true, special = Screen.colors.Red }, + [101] = { foreground = Screen.colors.Magenta1, bold = true }, } end) after_each(function() @@ -42,11 +43,12 @@ describe('ui/ext_messages', function() it('msg_clear follows msg_show kind of confirm', function() feed('iline 1<esc>') feed(':call confirm("test")<cr>') + local s1 = [[ + line ^1 | + {1:~ }|*4 + ]] screen:expect({ - grid = [[ - line ^1 | - {1:~ }|*4 - ]], + grid = s1, cmdline = { { content = { { '' } }, @@ -64,13 +66,7 @@ describe('ui/ext_messages', function() }, }) feed('<cr>') - screen:expect({ - grid = [[ - line ^1 | - {1:~ }|*4 - ]], - cmdline = { { abort = false } }, - }) + screen:expect({ grid = s1, cmdline = { { abort = false } } }) end) it('msg_show kinds', function() @@ -78,12 +74,13 @@ describe('ui/ext_messages', function() -- confirm is now cmdline prompt feed(':echo confirm("test")<cr>') + local s1 = [[ + line 1 | + line ^2 | + {1:~ }|*3 + ]] screen:expect({ - grid = [[ - line 1 | - line ^2 | - {1:~ }|*3 - ]], + grid = s1, cmdline = { { content = { { '' } }, @@ -102,11 +99,7 @@ describe('ui/ext_messages', function() }) feed('<cr>') screen:expect({ - grid = [[ - line 1 | - line ^2 | - {1:~ }|*3 - ]], + grid = s1, cmdline = { { abort = false } }, messages = { { @@ -168,13 +161,9 @@ describe('ui/ext_messages', function() }) -- kind=wmsg ('wrapscan' after search reaches EOF) - feed('uG$/i<cr>') + feed('uG$/i<CR>G$') screen:expect { - grid = [[ - l^ine 1 | - line 2 | - {1:~ }|*3 - ]], + grid = s1, cmdline = { { abort = false } }, messages = { { @@ -188,6 +177,7 @@ describe('ui/ext_messages', function() -- kind=emsg after :throw feed(':throw "foo"<cr>') screen:expect { + grid = s1, cmdline = { { abort = false } }, messages = { { @@ -211,13 +201,9 @@ describe('ui/ext_messages', function() -- kind=quickfix after :cnext feed('<c-c>') command("caddexpr [expand('%').':1:line1',expand('%').':2:line2']") - feed(':cnext<cr>') + feed(':cnext<CR>$') screen:expect { - grid = [[ - line 1 | - ^line 2 | - {1:~ }|*3 - ]], + grid = s1, cmdline = { { abort = false } }, messages = { { @@ -229,13 +215,9 @@ describe('ui/ext_messages', function() } -- search_cmd - feed('?line<cr>') + feed('?line<CR>G$') screen:expect({ - grid = [[ - ^line 1 | - line 2 | - {1:~ }|*3 - ]], + grid = s1, cmdline = { { abort = false } }, messages = { { @@ -247,8 +229,9 @@ describe('ui/ext_messages', function() }) -- highlight - feed(':filter character highlight<CR>') + feed('G$:filter character highlight<CR>') screen:expect({ + grid = s1, cmdline = { { abort = false } }, messages = { { @@ -286,12 +269,13 @@ describe('ui/ext_messages', function() }) feed('<C-r><C-r><C-r>') + local s2 = [[ + line 1 | + line^ | + {1:~ }|*3 + ]] screen:expect({ - grid = [[ - line 1 | - line^ | - {1:~ }|*3 - ]], + grid = s2, messages = { { content = { { 'Already at newest change' } }, @@ -305,6 +289,7 @@ describe('ui/ext_messages', function() command('set noshowmode') feed('i<C-n>') screen:expect({ + grid = s2, messages = { { content = { { 'The only match' } }, @@ -313,12 +298,13 @@ describe('ui/ext_messages', function() }, }, }) - feed('<Esc>') + feed('<Esc>l') command('set showmode') -- kind=echoerr for nvim_echo() err feed(':call nvim_echo([["Error"], ["Message", "Special"]], 1, #{ err:1 })<CR>') screen:expect({ + grid = s2, cmdline = { { abort = false } }, messages = { { @@ -332,6 +318,7 @@ describe('ui/ext_messages', function() -- kind=verbose for nvim_echo() verbose feed(':call nvim_echo([["Verbose Message"]], 1, #{ verbose:1 })<CR>') screen:expect({ + grid = s2, cmdline = { { abort = false } }, messages = { { @@ -345,6 +332,7 @@ describe('ui/ext_messages', function() -- kind=verbose for :verbose messages feed(':1verbose filter Diff[AC] hi<CR>') screen:expect({ + grid = s2, cmdline = { { abort = false } }, messages = { { @@ -391,11 +379,54 @@ describe('ui/ext_messages', function() }, }) + feed('<CR>') + n.add_builddir_to_rtp() + feed(':help<CR>:tselect<CR>') + local tagfile = t.paths.test_build_dir .. '/runtime/doc/help.txt' + if t.is_os('win') then + tagfile = tagfile:gsub('/', '\\') + end + screen:expect({ + grid = [[ + ^*help.txt* Nvim | + | + {3:help.txt [Help][RO] }| + line | + {2:<i_messages_spec [+][RO] }| + ]], + cmdline = { + { + content = { { '' } }, + hl_id = 0, + pos = 0, + prompt = 'Type number and <Enter> (q or empty cancels): ', + }, + }, + messages = { + { + content = { + { ' # pri kind tag', 101, 23 }, + { '\n ' }, + { 'file\n', 101, 23 }, + { '> 1 F ' }, + { 'help.txt', 101, 23 }, + { ' \n ' }, + { tagfile, 18, 5 }, + { '\n *help.txt*\n' }, + }, + history = false, + kind = 'confirm', + }, + }, + }) + feed('<CR>:bd<CR>') + -- kind=shell for :!cmd messages local cmd = t.is_os('win') and 'echo stdout& echo stderr>&2& exit 3' or '{ echo stdout; echo stderr >&2; exit 3; }' - feed(('<CR>:!%s<CR>'):format(cmd)) + feed((':!%s<CR>'):format(cmd)) screen:expect({ + grid = s2, cmdline = { { abort = false } }, messages = { { @@ -425,6 +456,41 @@ describe('ui/ext_messages', function() }, }, }) + + feed('<CR>:registers .<CR>') + screen:expect({ + grid = s2, + cmdline = { { + abort = false, + } }, + messages = { + { + content = { { '\nType Name Content', 101, 23 }, { '\n c ". ' } }, + history = false, + kind = 'list_cmd', + }, + }, + }) + + feed(':au ChanInfo * foo<CR>:au ChanInfo<CR>') + screen:expect({ + grid = s2, + cmdline = { { + abort = false, + } }, + messages = { + { + content = { + { '\n--- Autocommands ---', 101, 23 }, + { '\n' }, + { 'ChanInfo', 101, 23 }, + { '\n*foo' }, + }, + history = false, + kind = 'list_cmd', + }, + }, + }) end) it(':echoerr', function() @@ -1266,7 +1332,7 @@ stack traceback: { content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } }, history = false, - kind = 'list_cmd', + kind = 'confirm', }, }, }) @@ -1289,7 +1355,7 @@ stack traceback: { content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } }, history = false, - kind = 'list_cmd', + kind = 'confirm', }, }, }) @@ -1321,7 +1387,7 @@ stack traceback: { content = { { 'input0\ninput1\n' } }, history = false, - kind = 'list_cmd', + kind = 'confirm', }, }, }) diff --git a/test/old/testdir/setup.vim b/test/old/testdir/setup.vim index 9bb2dd4e0a..87287a57ff 100644 --- a/test/old/testdir/setup.vim +++ b/test/old/testdir/setup.vim @@ -3,8 +3,8 @@ if exists('s:did_load') set commentstring=/*\ %s\ */ set complete=.,w,b,u,t,i set define=^\\s*#\\s*define - set directory^=. set diffopt=internal,filler,closeoff + set directory^=. set display= set fillchars=vert:\|,foldsep:\|,fold:- set formatoptions=tcq diff --git a/test/old/testdir/test_edit.vim b/test/old/testdir/test_edit.vim index 9114cf8b11..bfb1790a9b 100644 --- a/test/old/testdir/test_edit.vim +++ b/test/old/testdir/test_edit.vim @@ -200,7 +200,7 @@ func Test_edit_07() endif endfu au InsertCharPre <buffer> :call DoIt() - call feedkeys("A\<f5>\<c-p>u\<cr>\<c-l>\<cr>", 'tx') + call feedkeys("A\<f5>\<c-p>u\<C-Y>\<c-l>\<cr>", 'tx') call assert_equal(["Jan\<c-l>",''], 1->getline('$')) %d call setline(1, 'J') @@ -472,7 +472,7 @@ func Test_edit_CR() " has been taken care of by other tests CheckFeature quickfix botright new - call writefile(range(1, 10), 'Xqflist.txt') + call writefile(range(1, 10), 'Xqflist.txt', 'D') call setqflist([{'filename': 'Xqflist.txt', 'lnum': 2}]) copen set modifiable @@ -492,16 +492,15 @@ func Test_edit_CR() call feedkeys("A\n", 'tnix') call feedkeys("A\r", 'tnix') call assert_equal(map(range(1, 10), 'string(v:val)') + ['', '', '', ''], getline(1, '$')) + bw! lclose - call delete('Xqflist.txt') endfunc func Test_edit_CTRL_() + CheckFeature rightleft " disabled for Windows builds, why? - if !has("rightleft") || has("win32") - return - endif + CheckNotMSWindows let _encoding=&encoding set encoding=utf-8 " Test for CTRL-_ @@ -592,7 +591,7 @@ func Test_edit_CTRL_I() call feedkeys("Arunt\<c-x>\<c-f>\<tab>\<cr>\<esc>", 'tnix') call assert_match('runtest\.vim', getline(1)) %d - call writefile(['one', 'two', 'three'], 'Xinclude.txt') + call writefile(['one', 'two', 'three'], 'Xinclude.txt', 'D') let include='#include Xinclude.txt' call setline(1, [include, '']) call cursor(2, 1) @@ -604,14 +603,13 @@ func Test_edit_CTRL_I() call assert_equal([include, 'three', ''], getline(1, '$')) call feedkeys("2ggC\<c-x>\<tab>\<down>\<down>\<down>\<cr>\<esc>", 'tnix') call assert_equal([include, '', ''], getline(1, '$')) - call delete("Xinclude.txt") bw! endfunc func Test_edit_CTRL_K() " Test pressing CTRL-K (basically only dictionary completion and digraphs " the rest is already covered - call writefile(['A', 'AA', 'AAA', 'AAAA'], 'Xdictionary.txt') + call writefile(['A', 'AA', 'AAA', 'AAAA'], 'Xdictionary.txt', 'D') set dictionary=Xdictionary.txt new call setline(1, 'A') @@ -667,7 +665,6 @@ func Test_edit_CTRL_K() " error sleeps 2 seconds, when v:testing is not set let v:testing = 0 endtry - call delete('Xdictionary.txt') if exists('*test_override') call test_override("char_avail", 1) @@ -867,7 +864,7 @@ func Test_edit_CTRL_T() call assert_equal(["\<tab>abcxyz"], getline(1, '$')) set nopaste " CTRL-X CTRL-T (thesaurus complete) - call writefile(['angry furious mad enraged'], 'Xthesaurus') + call writefile(['angry furious mad enraged'], 'Xthesaurus', 'D') set thesaurus=Xthesaurus call setline(1, 'mad') call cursor(1, 1) @@ -924,13 +921,12 @@ func Test_edit_CTRL_T() let v:testing = 0 endtry call assert_equal(['mad'], getline(1, '$')) - call delete('Xthesaurus') bw! endfunc " Test thesaurus completion with different encodings func Test_thesaurus_complete_with_encoding() - call writefile(['angry furious mad enraged'], 'Xthesaurus') + call writefile(['angry furious mad enraged'], 'Xthesaurus', 'D') set thesaurus=Xthesaurus " for e in ['latin1', 'utf-8'] for e in ['utf-8'] @@ -943,7 +939,6 @@ func Test_thesaurus_complete_with_encoding() bw! endfor set thesaurus= - call delete('Xthesaurus') endfunc " Test 'thesaurusfunc' @@ -1524,7 +1519,7 @@ func Test_edit_complete_very_long_name() let dirname = getcwd() . "/Xlongdir" let longdirname = dirname . repeat('/' . repeat('d', 255), 4) try - call mkdir(longdirname, 'p') + call mkdir(longdirname, 'pR') catch /E739:/ " Long directory name probably not supported. call delete(dirname, 'rf') @@ -1564,7 +1559,6 @@ func Test_edit_complete_very_long_name() bwipe! exe 'bwipe! ' . longfilename - call delete(dirname, 'rf') let &columns = save_columns if winposx >= 0 && winposy >= 0 exe 'winpos ' . winposx . ' ' . winposy @@ -1783,19 +1777,18 @@ endfunc " Test for editing a file using invalid file encoding func Test_edit_invalid_encoding() CheckEnglish - call writefile([], 'Xinvfile') + call writefile([], 'Xinvfile', 'D') redir => msg new ++enc=axbyc Xinvfile redir END call assert_match('\[NOT converted\]', msg) - call delete('Xinvfile') close! endfunc " Test for the "charconvert" option func Test_edit_charconvert() CheckEnglish - call writefile(['one', 'two'], 'Xccfile') + call writefile(['one', 'two'], 'Xccfile', 'D') " set 'charconvert' to a non-existing function set charconvert=NonExitingFunc() @@ -1855,8 +1848,6 @@ func Test_edit_charconvert() close! delfunc Cconv3 set charconvert& - - call delete('Xccfile') endfunc " Test for editing a file without read permission @@ -1864,7 +1855,7 @@ func Test_edit_file_no_read_perm() CheckUnix CheckNotRoot - call writefile(['one', 'two'], 'Xnrpfile') + call writefile(['one', 'two'], 'Xnrpfile', 'D') call setfperm('Xnrpfile', '-w-------') new redir => msg @@ -1874,7 +1865,6 @@ func Test_edit_file_no_read_perm() call assert_equal([''], getline(1, '$')) call assert_match('\[Permission Denied\]', msg) close! - call delete('Xnrpfile') endfunc " Using :edit without leaving 'insertmode' should not cause Insert mode to be @@ -1886,19 +1876,24 @@ func Test_edit_insertmode_ex_edit() set insertmode noruler inoremap <C-B> <Cmd>edit Xfoo<CR> END - call writefile(lines, 'Xtest_edit_insertmode_ex_edit') - - let buf = RunVimInTerminal('-S Xtest_edit_insertmode_ex_edit', #{rows: 6}) - " Somehow this can be very slow with valgrind. A separate TermWait() works - " better than a longer time with WaitForAssert() (why?) - call TermWait(buf, 1000) + call writefile(lines, 'Xtest_edit_insertmode_ex_edit', 'D') + + let buf = RunVimInTerminal('-S Xtest_edit_insertmode_ex_edit', #{rows: 6, wait_for_ruler: 0}) + " Somehow when using valgrind "INSERT" does not show up unless we send + " something to the terminal. + for i in range(30) + if term_getline(buf, 6) =~ 'INSERT' + break + endif + call term_sendkeys(buf, "-") + sleep 100m + endfor call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, 6))}) call term_sendkeys(buf, "\<C-B>\<C-L>") call WaitForAssert({-> assert_notmatch('^-- INSERT --\s*$', term_getline(buf, 6))}) " clean up call StopVimInTerminal(buf) - call delete('Xtest_edit_insertmode_ex_edit') endfunc " Pressing escape in 'insertmode' should beep @@ -2124,6 +2119,32 @@ func Test_edit_overlong_file_name() bwipe! endfunc +func Test_edit_shift_bs() + CheckMSWindows + + " FIXME: this works interactively, but the test fails + throw 'Skipped: Shift-Backspace Test not working correctly :(' + + " Need to run this in Win32 Terminal, do not use CheckRunVimInTerminal + if !has("terminal") + return + endif + + " Shift Backspace should work like Backspace in insert mode + let lines =<< trim END + call setline(1, ['abc']) + END + call writefile(lines, 'Xtest_edit_shift_bs', 'D') + + let buf = RunVimInTerminal('-S Xtest_edit_shift_bs', #{rows: 3}) + call term_sendkeys(buf, "A\<S-BS>-\<esc>") + call TermWait(buf, 50) + call assert_equal('ab-', term_getline(buf, 1)) + + " clean up + call StopVimInTerminal(buf) +endfunc + func Test_edit_Ctrl_RSB() new let g:triggered = [] @@ -2288,4 +2309,15 @@ func Test_edit_backspace_smarttab_virtual_text() set smarttab& endfunc +func Test_edit_CAR() + set cot=menu,menuone,noselect + new + + call feedkeys("Shello hero\<CR>h\<C-x>\<C-N>e\<CR>", 'tx') + call assert_equal(['hello hero', 'he', ''], getline(1, '$')) + + bw! + set cot& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 1260f01f8e..eb261077a3 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -672,7 +672,7 @@ func s:GetFilenameChecks() abort \ 'sass': ['file.sass'], \ 'sbt': ['file.sbt'], \ 'scala': ['file.scala', 'file.mill'], - \ 'scheme': ['file.scm', 'file.ss', 'file.sld', 'file.stsg', 'any/local/share/supertux2/config', '.lips_repl_history'], + \ 'scheme': ['file.scm', 'file.ss', 'file.sld', 'file.stsg', 'any/local/share/supertux2/config', '.lips_repl_history', '.guile'], \ 'scilab': ['file.sci', 'file.sce'], \ 'screen': ['.screenrc', 'screenrc'], \ 'scss': ['file.scss'], @@ -880,7 +880,8 @@ func s:GetFilenameChecks() abort \ 'xf86conf': ['xorg.conf', 'xorg.conf-4'], \ 'xhtml': ['file.xhtml', 'file.xht'], \ 'xinetd': ['/etc/xinetd.conf', '/etc/xinetd.d/file', 'any/etc/xinetd.conf', 'any/etc/xinetd.d/file'], - \ 'xkb': ['/usr/share/X11/xkb/compat/pc', '/usr/share/X11/xkb/geometry/pc', '/usr/share/X11/xkb/keycodes/evdev', '/usr/share/X11/xkb/symbols/pc', '/usr/share/X11/xkb/types/pc'], + \ 'xkb': ['any/xkb/compat/pc', 'any/xkb/geometry/pc', 'any/xkb/keycodes/evdev', 'any/xkb/symbols/pc', 'any/xkb/types/pc', + \ 'any/.xkb/compat/pc', 'any/.xkb/geometry/pc', 'any/.xkb/keycodes/evdev', 'any/.xkb/symbols/pc', 'any/.xkb/types/pc'], \ 'xmath': ['file.msc', 'file.msf'], \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.fsproj', 'file.fsproj.user', 'file.vbproj', 'file.vbproj.user', 'file.ui', \ 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', diff --git a/test/old/testdir/test_findfile.vim b/test/old/testdir/test_findfile.vim index 539c7a661a..62cdd767be 100644 --- a/test/old/testdir/test_findfile.vim +++ b/test/old/testdir/test_findfile.vim @@ -222,6 +222,36 @@ func Test_finddir_error() call assert_fails('call finddir("x", repeat("x", 5000))', 'E854:') endfunc +func Test_findfile_with_suffixesadd() + let save_path = &path + let save_dir = getcwd() + set path=,, + call mkdir('Xfinddir1', 'pR') + cd Xfinddir1 + + call writefile([], 'foo.c', 'D') + call writefile([], 'bar.cpp', 'D') + call writefile([], 'baz.cc', 'D') + call writefile([], 'foo.o', 'D') + call writefile([], 'bar.o', 'D') + call writefile([], 'baz.o', 'D') + + set suffixesadd=.c,.cpp + call assert_equal('foo.c', findfile('foo')) + call assert_equal('./foo.c', findfile('./foo')) + call assert_equal('bar.cpp', findfile('bar')) + call assert_equal('./bar.cpp', findfile('./bar')) + call assert_equal('', findfile('baz')) + call assert_equal('', findfile('./baz')) + set suffixesadd+=.cc + call assert_equal('baz.cc', findfile('baz')) + call assert_equal('./baz.cc', findfile('./baz')) + + set suffixesadd& + call chdir(save_dir) + let &path = save_path +endfunc + " Test for the :find, :sfind and :tabfind commands func Test_find_cmd() new diff --git a/test/old/testdir/test_gf.vim b/test/old/testdir/test_gf.vim index 9824c8276e..0e5d8407bc 100644 --- a/test/old/testdir/test_gf.vim +++ b/test/old/testdir/test_gf.vim @@ -361,4 +361,36 @@ func Test_gf_switchbuf() %bw! endfunc +func Test_gf_with_suffixesadd() + let cwd = getcwd() + let dir = 'Xtestgf_sua_dir' + call mkdir(dir, 'R') + call chdir(dir) + + call writefile([], 'foo.c', 'D') + call writefile([], 'bar.cpp', 'D') + call writefile([], 'baz.cc', 'D') + call writefile([], 'foo.o', 'D') + call writefile([], 'bar.o', 'D') + call writefile([], 'baz.o', 'D') + + new + setlocal path=,, suffixesadd=.c,.cpp + call setline(1, ['./foo', './bar', './baz']) + exe "normal! gg\<C-W>f" + call assert_equal('foo.c', expand('%:t')) + close + exe "normal! 2gg\<C-W>f" + call assert_equal('bar.cpp', expand('%:t')) + close + call assert_fails('exe "normal! 3gg\<C-W>f"', 'E447:') + setlocal suffixesadd+=.cc + exe "normal! 3gg\<C-W>f" + call assert_equal('baz.cc', expand('%:t')) + close + + %bwipe! + call chdir(cwd) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index f4dd2fbd10..cf9c970a22 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -2837,7 +2837,7 @@ func Test_complete_fuzzy_match() call setline(1, ['Text', 'ToText', '']) call cursor(2, 1) call feedkeys("STe\<C-X>\<C-N>x\<CR>\<Esc>0", 'tx!') - call assert_equal('Tex', getline('.')) + call assert_equal('Tex', getline(line('.') - 1)) " test case for nosort option set cot=menuone,menu,noinsert,fuzzy,nosort @@ -2873,6 +2873,14 @@ func Test_complete_fuzzy_match() call assert_equal("for", g:abbr) call assert_equal(2, g:selected) + set cot=menu,menuone,noselect,fuzzy + call feedkeys("i\<C-R>=CompAnother()\<CR>\<C-N>\<C-N>\<C-N>\<C-N>", 'tx') + call assert_equal("foo", g:word) + call feedkeys("i\<C-R>=CompAnother()\<CR>\<C-P>", 'tx') + call assert_equal("foo", g:word) + call feedkeys("i\<C-R>=CompAnother()\<CR>\<C-P>\<C-P>", 'tx') + call assert_equal("for", g:abbr) + " clean up set omnifunc= bw! @@ -3102,10 +3110,59 @@ function Test_completeopt_preinsert() call assert_equal("fobar", getline('.')) call assert_equal(5, col('.')) + set cot=preinsert + call feedkeys("Sfoo1 foo2\<CR>f\<C-X>\<C-N>bar", 'tx') + call assert_equal("fbar", getline('.')) + call assert_equal(4, col('.')) + bw! set cot& set omnifunc& delfunc Omni_test endfunc +" Check that mark positions are correct after triggering multiline completion. +func Test_complete_multiline_marks() + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [ + \ #{word: "func ()\n\t\nend"}, + \ #{word: "foobar"}, + \ #{word: "你好\n\t\n我好"} + \ ] + endfunc + set omnifunc=Omni_test + + new + let lines = mapnew(range(10), 'string(v:val)') + call setline(1, lines) + call setpos("'a", [0, 3, 1, 0]) + + call feedkeys("A \<C-X>\<C-O>\<C-E>\<BS>", 'tx') + call assert_equal(lines, getline(1, '$')) + call assert_equal([0, 3, 1, 0], getpos("'a")) + + call feedkeys("A \<C-X>\<C-O>\<C-N>\<C-E>\<BS>", 'tx') + call assert_equal(lines, getline(1, '$')) + call assert_equal([0, 3, 1, 0], getpos("'a")) + + call feedkeys("A \<C-X>\<C-O>\<C-N>\<C-N>\<C-E>\<BS>", 'tx') + call assert_equal(lines, getline(1, '$')) + call assert_equal([0, 3, 1, 0], getpos("'a")) + + call feedkeys("A \<C-X>\<C-O>\<C-N>\<C-N>\<C-N>\<C-E>\<BS>", 'tx') + call assert_equal(lines, getline(1, '$')) + call assert_equal([0, 3, 1, 0], getpos("'a")) + + call feedkeys("A \<C-X>\<C-O>\<C-Y>", 'tx') + call assert_equal(['0 func ()', "\t", 'end'] + lines[1:], getline(1, '$')) + call assert_equal([0, 5, 1, 0], getpos("'a")) + + bw! + set omnifunc& + delfunc Omni_test +endfunc + " vim: shiftwidth=2 sts=2 expandtab nofoldenable diff --git a/test/old/testdir/test_listchars.vim b/test/old/testdir/test_listchars.vim index b82b70746b..7965838671 100644 --- a/test/old/testdir/test_listchars.vim +++ b/test/old/testdir/test_listchars.vim @@ -647,7 +647,7 @@ func Test_listchars_foldcolumn() vsplit windo set signcolumn=yes foldcolumn=1 winminwidth=0 nowrap list listchars=extends:>,precedes:< END - call writefile(lines, 'XTest_listchars') + call writefile(lines, 'XTest_listchars', 'D') let buf = RunVimInTerminal('-S XTest_listchars', {'rows': 10, 'cols': 60}) @@ -670,8 +670,57 @@ func Test_listchars_foldcolumn() " clean up call StopVimInTerminal(buf) - call delete('XTest_listchars') endfunc +func Test_listchars_precedes_with_wide_char() + new + setlocal nowrap list listchars=eol:$,precedes:! + call setline(1, '123口456') + call assert_equal(['123口456$ '], ScreenLines(1, 10)) + let attr = screenattr(1, 9) + + normal! zl + call assert_equal(['!3口456$ '], ScreenLines(1, 10)) + call assert_equal(attr, screenattr(1, 1)) + normal! zl + call assert_equal(['!口456$ '], ScreenLines(1, 10)) + call assert_equal(attr, screenattr(1, 1)) + normal! zl + call assert_equal(['!<456$ '], ScreenLines(1, 10)) + call assert_equal(attr, screenattr(1, 1)) + call assert_equal(attr, screenattr(1, 2)) + normal! zl + call assert_equal(['!456$ '], ScreenLines(1, 10)) + call assert_equal(attr, screenattr(1, 1)) + normal! zl + call assert_equal(['!56$ '], ScreenLines(1, 10)) + call assert_equal(attr, screenattr(1, 1)) + normal! zl + call assert_equal(['!6$ '], ScreenLines(1, 10)) + call assert_equal(attr, screenattr(1, 1)) + + bw! +endfunc + +func Test_listchars_precedes_with_tab() + new + setlocal nowrap list listchars=eol:$,precedes:!,tab:<-> + call setline(1, "1234\t56") + let expected_line = '1234<-->56$ ' + call assert_equal([expected_line], ScreenLines(1, 12)) + let expected_attrs = mapnew(range(1, 12), 'screenattr(1, v:val)') + let attr = expected_attrs[-2] + + for i in range(8) + normal! zl + let expected_line = '!' .. expected_line[2:] .. ' ' + let expected_attrs = [attr] + expected_attrs[2:] + expected_attrs[-1:] + call assert_equal([expected_line], ScreenLines(1, 12)) + let attrs = mapnew(range(1, 12), 'screenattr(1, v:val)') + call assert_equal(expected_attrs, attrs) + endfor + + bw! +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index 7f80c60118..41b694860c 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -988,7 +988,7 @@ func Test_popup_complete_backwards() call setline(1, ['Post', 'Port', 'Po']) let expected=['Post', 'Port', 'Port'] call cursor(3,2) - call feedkeys("A\<C-X>". repeat("\<C-P>", 3). "rt\<cr>", 'tx') + call feedkeys("A\<C-X>". repeat("\<C-P>", 3). "rt\<C-Y>", 'tx') call assert_equal(expected, getline(1,'$')) bwipe! endfunc @@ -998,7 +998,7 @@ func Test_popup_complete_backwards_ctrl_p() call setline(1, ['Post', 'Port', 'Po']) let expected=['Post', 'Port', 'Port'] call cursor(3,2) - call feedkeys("A\<C-P>\<C-N>rt\<cr>", 'tx') + call feedkeys("A\<C-P>\<C-N>rt\<C-Y>", 'tx') call assert_equal(expected, getline(1,'$')) bwipe! endfunc diff --git a/test/old/testdir/test_startup_utf8.vim b/test/old/testdir/test_startup_utf8.vim index e8b99e7937..96b593f1bd 100644 --- a/test/old/testdir/test_startup_utf8.vim +++ b/test/old/testdir/test_startup_utf8.vim @@ -60,6 +60,34 @@ func Test_read_fifo_utf8() call delete('Xtestout') endfunc +func Test_detect_fifo() + CheckUnix + " Using bash/zsh's process substitution. + if executable('bash') + set shell=bash + elseif executable('zsh') + set shell=zsh + else + throw 'Skipped: bash or zsh is required' + endif + let linesin = ['one', 'two'] + call writefile(linesin, 'Xtestin_fifo', 'D') + let after = [ + \ 'call writefile(split(execute(":mess"), "\\n"), "Xtestout")', + \ 'quit!', + \ ] + " if RunVim([], after, '<(cat Xtestin_fifo)') + if RunVim(['set shortmess-=F'], after, '<(cat Xtestin_fifo)') + let lines = readfile('Xtestout') + call assert_match('\[fifo\]', lines[0]) + " call assert_match('\[fifo\]', lines[1]) + else + call assert_equal('', 'RunVim failed.') + endif + + call delete('Xtestout') +endfunc + func Test_detect_ambiwidth() CheckRunVimInTerminal diff --git a/test/unit/strings_spec.lua b/test/unit/strings_spec.lua index 2b7a4d6261..8df9837c90 100644 --- a/test/unit/strings_spec.lua +++ b/test/unit/strings_spec.lua @@ -156,7 +156,7 @@ describe('vim_snprintf()', function() eq(#expected, strings.vim_snprintf(buf, bsize, fmt, ...), ctx) if bsize > 0 then local actual = ffi.string(buf, math.min(#expected + 1, bsize)) - eq(expected:sub(1, bsize - 1) .. '\0', actual) + eq(expected:sub(1, bsize - 1) .. '\0', actual, ctx) end end diff --git a/test/unit/testutil.lua b/test/unit/testutil.lua index 4720d4d730..7fd0e73b47 100644 --- a/test/unit/testutil.lua +++ b/test/unit/testutil.lua @@ -146,11 +146,16 @@ local function filter_complex_blocks(body) or string.find(line, 'value_init_') or string.find(line, 'UUID_NULL') -- static const uuid_t UUID_NULL = {...} or string.find(line, 'inline _Bool') + -- used by musl libc headers on 32-bit arches via __REDIR marco + or string.find(line, '__typeof__') -- used by macOS headers or string.find(line, 'typedef enum : ') or string.find(line, 'mach_vm_range_recipe') ) then + -- Remove GCC's extension keyword which is just used to disable warnings. + line = string.gsub(line, '__extension__', '') + -- HACK: remove bitfields from specific structs as luajit can't seem to handle them. if line:find('struct VTermState') then line = string.gsub(line, 'state : 8;', 'state;') |