diff options
116 files changed, 3188 insertions, 1567 deletions
diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index fc4ddef54b..fef7496e20 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.2.tar.gz -TREESITTER_SHA256 26791f69182192fef179cd58501c3226011158823557a86fe42682cb4a138523 +TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.25.3.tar.gz +TREESITTER_SHA256 862fac52653bc7bc9d2cd0630483e6bdf3d02bcd23da956ca32663c4798a93e3 WASMTIME_URL https://github.com/bytecodealliance/wasmtime/archive/v29.0.1.tar.gz WASMTIME_SHA256 b94b6c6fd6aebaf05d4c69c1b12b5dc217b0d42c1a95f435b33af63dddfa5304 diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 413802eea1..3a579ab194 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -352,10 +352,14 @@ BufWritePost After writing the whole buffer to a file *ChanInfo* ChanInfo State of channel changed, for instance the client of a RPC channel described itself. + This is triggered even when inside an + autocommand defined without |autocmd-nested|. Sets these |v:event| keys: info as from |nvim_get_chan_info()| *ChanOpen* ChanOpen Just after a channel was opened. + This is triggered even when inside an + autocommand defined without |autocmd-nested|. Sets these |v:event| keys: info as from |nvim_get_chan_info()| *CmdUndefined* @@ -364,6 +368,8 @@ CmdUndefined When a user command is used but it isn't when it's used. The pattern is matched against the command name. Both <amatch> and <afile> expand to the command name. + This is triggered even when inside an + autocommand defined without |autocmd-nested|. NOTE: Autocompletion won't work until the command is defined. An alternative is to always define the user command and have it @@ -689,6 +695,8 @@ FuncUndefined When a user function is used but it isn't when it's used. The pattern is matched against the function name. Both <amatch> and <afile> are set to the name of the function. + This is triggered even when inside an + autocommand defined without |autocmd-nested|. NOTE: When writing Vim scripts a better alternative is to use an autoloaded function. See |autoload-functions|. @@ -910,7 +918,10 @@ ShellCmdPost After executing a shell command with |:!cmd|, *Signal* Signal After Nvim receives a signal. The pattern is matched against the signal name. Only - "SIGUSR1" and "SIGWINCH" are supported. Example: > + "SIGUSR1" and "SIGWINCH" are supported. + This is triggered even when inside an + autocommand defined without |autocmd-nested|. + Example: >vim autocmd Signal SIGUSR1 call some#func() < *ShellFilterPost* ShellFilterPost After executing a shell command with @@ -1004,22 +1015,40 @@ TermClose When a |terminal| job ends. status *TermRequest* TermRequest When a |:terminal| child process emits an OSC, - DCS or APC sequence. Sets |v:termrequest|. The - |event-data| is the request string. + DCS, or APC sequence. Sets |v:termrequest|. The + |event-data| is a table with the following + fields: + + - sequence: the received sequence + - cursor: (1,0)-indexed, buffer-relative + position of the cursor when the sequence was + received + + This is triggered even when inside an + autocommand defined without |autocmd-nested|. + *TermResponse* TermResponse When Nvim receives an OSC or DCS response from the host terminal. Sets |v:termresponse|. The - |event-data| is the response string. May be - triggered during another event (file I/O, - a shell command, or anything else that takes - time). Example: >lua + |event-data| is a table with the following fields: + + - sequence: the received sequence + + This is triggered even when inside an + autocommand defined without |autocmd-nested|. + + May be triggered during another event (file + I/O, a shell command, or anything else that + takes time). + + Example: >lua -- Query the terminal palette for the RGB value of color 1 -- (red) using OSC 4 vim.api.nvim_create_autocmd('TermResponse', { once = true, callback = function(args) - local resp = args.data + local resp = args.data.sequence local r, g, b = resp:match("\027%]4;1;rgb:(%w+)/(%w+)/(%w+)") end, }) @@ -1391,8 +1420,8 @@ vimrc file again). *FileExplorer* There is one group that is recognized by Vim: FileExplorer. If this group exists Vim assumes that editing a directory is possible and will trigger a -plugin that lists the files in that directory. This is used by the |netrw| -plugin. This allows you to do: > +plugin that lists the files in that directory. This is used by directory +browser plugins. This allows you to do: > browse edit ============================================================================== @@ -1666,7 +1695,7 @@ and "++ff=" argument that are effective. These should be used for the command that reads/writes the file. The |v:cmdbang| variable is one when "!" was used, zero otherwise. -See the $VIMRUNTIME/plugin/netrwPlugin.vim for examples. +See the $VIMRUNTIME/pack/dist/opt/netrw/plugin/netrwPlugin.vim for examples. ============================================================================== 11. Disabling autocommands *autocmd-disable* diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index 3fc17af185..45a95e439a 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -83,6 +83,10 @@ CTRL-Q Same as CTRL-V. But with some terminals it is used for CTRL-SHIFT-V *c_CTRL-SHIFT-V* *c_CTRL-SHIFT-Q* CTRL-SHIFT-Q Works just like CTRL-V, but do not try to include the CTRL modifier into the key. + Note: When CTRL-SHIFT-V is intercepted by your system (e.g., + to paste text) you can often use CTRL-SHIFT-Q instead. + However, in some terminals (e.g. GNOME Terminal), CTRL-SHIFT-Q + quits the terminal without confirmation. *c_<Left>* *c_Left* <Left> cursor left. See 'wildmenu' for behavior during wildmenu diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index d3170f114f..8d60d2c868 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -376,10 +376,10 @@ Where possible, these patterns apply to _both_ Lua and the API: - See |vim.lsp.inlay_hint.enable()| and |vim.lsp.inlay_hint.is_enabled()| for a reference implementation of these "best practices". - NOTE: open questions: https://github.com/neovim/neovim/issues/28603 -- Transformation functions should also have a filter functionality when - appropriate. That is, when the function returns a nil value it "filters" its - input, otherwise the transformed value is used. - - Example: |vim.diagnostic.config.format()| +- Transformation functions should also have "filter" functionality (when + appropriate): when the function returns a nil value it excludes (filters + out) its input, else the transformed value is used. + - Example: See the format() field of |vim.diagnostic.Opts.Float|. API DESIGN GUIDELINES *dev-api* diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 4224709a39..c275bf863d 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -1496,9 +1496,8 @@ Or, when starting gvim from a shell: > Note that if a FileChangedShell autocommand is defined you will not get a warning message or prompt. The autocommand is expected to handle this. -There is no warning for a directory (e.g., with |netrw-browse|). But you do -get warned if you started editing a new file and it was created as a directory -later. +There is no warning for a directory. But you do get warned if you started +editing a new file and it was created as a directory later. When Vim notices the timestamp of a file has changed, and the file is being edited in a buffer but has not changed, Vim checks if the contents of the file diff --git a/runtime/doc/help.txt b/runtime/doc/help.txt index 914dc64c0a..991b3d02c8 100644 --- a/runtime/doc/help.txt +++ b/runtime/doc/help.txt @@ -180,11 +180,11 @@ Standard plugins ~ *standard-plugin-list* |pi_gzip.txt| Reading and writing compressed files |pi_msgpack.txt| msgpack utilities -|pi_netrw.txt| Reading and writing files over a network |pi_paren.txt| Highlight matching parens |pi_spec.txt| Filetype plugin to work with rpm spec files |pi_tar.txt| Tar file explorer |pi_zip.txt| Zip archive explorer +|netrw| Reading and writing files over a network Local additions ~ *local-additions* diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index 0256707420..b814b3be78 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -786,8 +786,7 @@ tag char note action in Normal mode ~ |gu| gu{motion} 2 make Nmove text lowercase |gv| gv reselect the previous Visual area |gw| gw{motion} 2 format Nmove text and keep cursor -|netrw-gx| gx execute application for file name under the - cursor (only with |netrw| plugin) +|gx| gx execute application for filepath at cursor |g@| g@{motion} call 'operatorfunc' |g~| g~{motion} 2 swap case for Nmove text |g<Down>| g<Down> 1 same as "gj" diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index 06f1a4e73d..b77339a325 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -212,6 +212,10 @@ CTRL-Q Same as CTRL-V. CTRL-SHIFT-V *i_CTRL-SHIFT-V* *i_CTRL-SHIFT-Q* CTRL-SHIFT-Q Works just like CTRL-V, but do not try to include the CTRL modifier into the key. + Note: When CTRL-SHIFT-V is intercepted by your system (e.g., + to paste text) you can often use CTRL-SHIFT-Q instead. + However, in some terminals (e.g. GNOME Terminal), CTRL-SHIFT-Q + quits the terminal without confirmation. CTRL-X Enter CTRL-X mode. This is a sub-mode where commands can be given to complete words or scroll the window. See @@ -639,8 +643,8 @@ Also see the 'infercase' option if you want to adjust the case of the match. When inserting a selected candidate word from the |popup-menu|, the part of the candidate word that does not match the query is highlighted using -|hl-ComplMatchIns|. If fuzzy is enabled in 'completopt', highlighting will not -be applied. +|hl-ComplMatchIns|. If fuzzy is enabled in 'completeopt', highlighting will +not be applied. *complete_CTRL-E* When completion is active you can use CTRL-E to stop it and go back to the diff --git a/runtime/doc/lua-bit.txt b/runtime/doc/lua-bit.txt index 4c47010113..dde235d141 100644 --- a/runtime/doc/lua-bit.txt +++ b/runtime/doc/lua-bit.txt @@ -86,19 +86,17 @@ Bit operations 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. + normalize all of their input arguments. See |lua-bit-semantics|. Example: >lua - print(0xffffffff) --> 4294967295 (*) + print(0xffffffff) --> 4294967295 (see Note) 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). + Note: |lua-bit-hex-literals| explains why the numbers printed 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 @@ -369,7 +367,7 @@ strongly advised not to rely on undefined or implementation-defined behavior. - 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 + 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 diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index c184e4792d..fa3367602d 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -289,7 +289,7 @@ arguments separated by " " (space) instead of "\t" (tab). :lua {chunk} Executes Lua chunk {chunk}. If {chunk} starts with "=" the rest of the chunk is evaluated as an expression and printed. `:lua =expr` and `:=expr` - are equivalent to `:lua vim.print(expr)`. + are equivalent to `:lua print(vim.inspect(expr))`. Examples: >vim :lua vim.api.nvim_command('echo "Hello, Nvim!"') diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt index 284be09121..48fa723800 100644 --- a/runtime/doc/motion.txt +++ b/runtime/doc/motion.txt @@ -455,6 +455,8 @@ between Vi and Vim. first column. When used after an operator, then also stops below a "}" in the first column. |exclusive| Note that |exclusive-linewise| often applies. + In a :terminal buffer each shell prompt is treated as + a section. |terminal_]]| *][* ][ [count] |section|s forward or to the next '}' in the @@ -465,6 +467,8 @@ between Vi and Vim. [[ [count] |section|s backward or to the previous "{" in the first column. |exclusive| Note that |exclusive-linewise| often applies. + In a :terminal buffer each shell prompt is treated as + a section. |terminal_]]| *[]* [] [count] |section|s backward or to the previous "}" in @@ -498,6 +502,7 @@ A section begins after a form-feed (<C-L>) in the first column and at each of a set of section macros, specified by the pairs of characters in the 'sections' option. The default is "SHNHH HUnhsh", which defines a section to start at the nroff macros ".SH", ".NH", ".H", ".HU", ".nh" and ".sh". +In a :terminal buffer each shell prompt is treated as a section. |terminal_]]| The "]]" and "[[" commands stop at the '{' in the first column. This is useful to find the start of a function in a C program. To search for a '}' in diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index a131934a8e..5a3e35a3dd 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -111,6 +111,9 @@ EVENTS • `history` argument indicating if the message was added to the history. • new message kinds: "bufwrite", "completion", "list_cmd", "lua_print", "search_cmd", "shell_out/err/ret", "undo", "verbose", wildlist". +• |TermRequest| and |TermResponse| |event-data| is now a table. The "sequence" + field contains the received sequence. |TermRequest| also contains a "cursor" + field indicating the cursor's position when the sequence was received. HIGHLIGHTS @@ -241,6 +244,8 @@ DEFAULTS • |[a|, |]a|, |[A|, |]A| navigate through the |argument-list| • |[b|, |]b|, |[B|, |]B| navigate through the |buffer-list| • |[<Space>|, |]<Space>| add an empty line above and below the cursor + • |[[| and |]]| in Normal mode jump between shell prompts for shells which emit + OSC 133 sequences ("shell integration" or "semantic prompts"). • Snippet: • `<Tab>` in Insert and Select mode maps to `vim.snippet.jump({ direction = 1 })` @@ -306,6 +311,8 @@ LSP • |vim.lsp.config()| has been added to define default configurations for servers. In addition, configurations can be specified in `lsp/<name>.lua`. • |vim.lsp.enable()| has been added to enable servers. +• |vim.lsp.buf.code_action()| resolves the `command` property during the + `codeAction/resolve` request. LUA @@ -329,6 +336,7 @@ OPTIONS • 'completeopt' flag "fuzzy" enables |fuzzy-matching| during |ins-completion|. • 'completeopt' flag "preinsert" highlights text to be inserted. +• 'wildmode' flag "noselect" shows 'wildmenu' without selecting an entry. • 'messagesopt' configures |:messages| and |hit-enter| prompt. • 'tabclose' controls which tab page to focus when closing a tab page. • 'eventignorewin' to persistently ignore events in a window. @@ -395,6 +403,8 @@ TERMINAL codes" mode is currently supported. • The |terminal| emits a |TermRequest| autocommand event when the child process emits an APC control sequence. +• |TermRequest| has a "cursor" field in its |event-data| indicating the + cursor position when the sequence was received. TREESITTER diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index a6f5f85d37..e42fb4f9e4 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1556,15 +1556,15 @@ A jump table for the options with a short description can be found at |Q_op|. match, e.g., what file it comes from. noinsert Do not insert any text for a match until the user selects - a match from the menu. Only works in combination with + a match from the menu. Only works in combination with "menu" or "menuone". No effect if "longest" is present. noselect Same as "noinsert", except that no menu item is - pre-selected. If both "noinsert" and "noselect" are + pre-selected. If both "noinsert" and "noselect" are present, "noselect" has precedence. nosort Disable sorting of completion candidates based on fuzzy - scores when "fuzzy" is enabled. Candidates will appear + scores when "fuzzy" is enabled. Candidates will appear in their original order. popup Show extra information about the currently selected @@ -1575,7 +1575,7 @@ A jump table for the options with a short description can be found at |Q_op|. Preinsert the portion of the first candidate word that is not part of the current completion leader and using the |hl-ComplMatchIns| highlight group. In order for it to - work, "fuzzy" must not bet set and "menuone" must be set. + work, "fuzzy" must not be set and "menuone" must be set. preview Show extra information about the currently selected completion in the preview window. Only works in @@ -4505,7 +4505,7 @@ A jump table for the options with a short description can be found at |Q_op|. set path=,, < - A directory name may end in a ':' or '/'. - Environment variables are expanded |:set_env|. - - When using |netrw.vim| URLs can be used. For example, adding + - When using |netrw| URLs can be used. For example, adding "https://www.vim.org" will make ":find index.html" work. - Search upwards and downwards in a directory tree using "*", "**" and ";". See |file-searching| for info and syntax. @@ -7050,7 +7050,10 @@ A jump table for the options with a short description can be found at |Q_op|. "lastused" When completing buffer names and more than one buffer matches, sort buffers by time last used (other than the current buffer). - When there is only a single match, it is fully completed in all cases. + "noselect" Do not pre-select first menu item and start 'wildmenu' + if it is enabled. + When there is only a single match, it is fully completed in all cases + except when "noselect" is present. Examples of useful colon-separated values: "longest:full" Like "longest", but also start 'wildmenu' if it is @@ -7073,7 +7076,11 @@ A jump table for the options with a short description can be found at |Q_op|. set wildmode=list,full < List all matches without completing, then each full match >vim set wildmode=longest,list -< Complete longest common string, then list alternatives. +< Complete longest common string, then list alternatives >vim + set wildmode=noselect:full +< Display 'wildmenu' without completing, then each full match >vim + set wildmode=noselect:lastused,full +< Same as above, but sort buffers by time last used. More info here: |cmdline-completion|. *'wildoptions'* *'wop'* diff --git a/runtime/doc/pi_gzip.txt b/runtime/doc/pi_gzip.txt index e8a1de9cef..65e7942562 100644 --- a/runtime/doc/pi_gzip.txt +++ b/runtime/doc/pi_gzip.txt @@ -29,6 +29,7 @@ with these extensions: extension compression > *.bz2 bzip2 + *.bz3 bzip3 *.gz gzip *.lz lzip *.lz4 lz4 diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt index 69ae0f20d1..24ec170319 100644 --- a/runtime/doc/provider.txt +++ b/runtime/doc/provider.txt @@ -264,7 +264,8 @@ causes the terminal emulator to write to or read from the system clipboard. When Nvim is running in the |TUI|, it will automatically attempt to determine if the host terminal emulator supports OSC 52. If it does, then Nvim will use OSC 52 for copying and pasting if no other |clipboard-tool| is found and when -'clipboard' is unset. +'clipboard' is unset. NOTE: Using a terminal multiplexer (e.g. tmux) may +inhibit automatic OSC 52 support detection. *g:termfeatures* To disable the automatic detection, set the "osc52" key of |g:termfeatures| to diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 3a19ee55c3..00486a49db 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -246,12 +246,6 @@ Added added line in a diff Changed changed line in a diff Removed removed line in a diff -The names marked with * are the preferred groups; the others are minor groups. -For the preferred groups, the "syntax.vim" file contains default highlighting. -The minor groups are linked to the preferred groups, so they get the same -highlighting. You can override these defaults by using ":highlight" commands -after sourcing the "syntax.vim" file. - Note that highlight group names are not case sensitive. "String" and "string" can be used for the same group. @@ -4816,16 +4810,20 @@ is mostly used, because it looks better. ============================================================================== 13. Highlight command *:highlight* *:hi* *E28* *E411* *E415* -There are two types of highlight groups: -- The built-in |highlight-groups|. -- The ones used for specific languages. For these the name starts with the - name of the language. Many of these don't have any attributes, but are - linked to a group of the second type. - *hitest.vim* -You can see all the groups currently active with this command: > - :so $VIMRUNTIME/syntax/hitest.vim -This will open a new window containing all highlight group names, displayed -in their own color. +Nvim uses a range of highlight groups which fall into two categories: Editor +interface and syntax highlighting. In rough order of importance, these are +- basic editor |highlight-groups| +- standard syntax |group-name|s (in addition, syntax files can define + language-specific groups, which are prefixed with the language name) +- |diagnostic-highlights| +- |treesitter-highlight-groups| +- |lsp-semantic-highlight| groups +- |lsp-highlight| of symbols and references + +Where appropriate, highlight groups are linked by default to one of the more basic +groups, but colorschemes are expected to cover all of them. Under each tag, +the corresponding highlight groups are highlighted using the current +colorscheme. *:colo* *:colorscheme* *E185* :colo[rscheme] Output the name of the currently active color scheme. diff --git a/runtime/doc/terminal.txt b/runtime/doc/terminal.txt index 0ab7151728..ea8a1c2565 100644 --- a/runtime/doc/terminal.txt +++ b/runtime/doc/terminal.txt @@ -144,8 +144,8 @@ directory indicated in the request. >lua vim.api.nvim_create_autocmd({ 'TermRequest' }, { desc = 'Handles OSC 7 dir change requests', callback = function(ev) - if string.sub(vim.v.termrequest, 1, 4) == '\x1b]7;' then - local dir = string.gsub(vim.v.termrequest, '\x1b]7;file://[^/]*', '') + if string.sub(ev.data.sequence, 1, 4) == '\x1b]7;' then + local dir = string.gsub(ev.data.sequence, '\x1b]7;file://[^/]*', '') if vim.fn.isdirectory(dir) == 0 then vim.notify('invalid dir: '..dir) return @@ -185,6 +185,54 @@ clipboard. OSC 52 sequences sent from the :terminal buffer do not emit a |TermRequest| event. The event is handled directly by Nvim and is not forwarded to plugins. +OSC 133: shell integration *terminal-osc133* + +Shells can emit semantic escape sequences (OSC 133) to mark where each prompt +starts and ends. The start of a prompt is marked by sequence `OSC 133 ; A ST`, +and the end by `OSC 133 ; B ST`. + + *shell-prompt-config* +You can configure your shell "rc" (e.g. ~/.bashrc) to emit OSC 133 sequences, +or your terminal may attempt to do it for you (assuming your shell config +doesn't interfere). + +- fish: https://fishshell.com/docs/current/relnotes.html#improved-terminal-support +- kitty: https://sw.kovidgoyal.net/kitty/shell-integration/ +- vscode: https://code.visualstudio.com/docs/terminal/shell-integration + +To configure bash to mark the start/end of each prompt, set $PROMPT_COMMAND +and $PS1 as follows: >bash + + # Prompt start: + PROMPT_COMMAND='printf "\033]133;A\007"' + # Prompt end: + PS1="$PS1"'\033]133;B\007' +< + *terminal_]]* *terminal_[[* +The |]]| and |[[| motions jump to the next/previous prompts, if your shell +emits OSC 133 as described above. + + *terminal-shell-prompt-signs* +To annotate each terminal prompt with a sign, call |nvim_buf_set_extmark()| +from a |TermRequest| handler: >lua + + local ns = vim.api.nvim_create_namespace('my.terminal.prompt') + vim.api.nvim_create_autocmd('TermRequest', { + callback = function(args) + if string.match(args.data.sequence, '^\027]133;A') then + local lnum = args.data.cursor[1] + vim.api.nvim_buf_set_extmark(args.buf, ns, lnum - 1, 0, { + sign_text = '▶', + sign_hl_group = 'SpecialChar', + }) + end + end, + }) + -- Enable signcolumn in terminal buffers. + vim.api.nvim_create_autocmd('TermOpen', { + command = 'setlocal signcolumn=auto', + }) +< ============================================================================== Status Variables *terminal-status* diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index f0172c36e5..31335b4fbb 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -789,7 +789,7 @@ must handle. "" (empty) Unknown (consider a |feature-request|) "bufwrite" |:write| message "confirm" Message preceding a prompt (|:confirm|, - |confirm()|, |inputlist()|, |z=,|, …) + |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 1e1f6c4db7..8d8b97fe31 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -34,7 +34,8 @@ Defaults *defaults* *nvim-defaults* looking differently due to them relying on how highlight groups are defined by default. Add ":colorscheme vim" to |init.vim| or ":source $VIMRUNTIME/colors/vim.lua" to your color scheme file to restore - the old default links and colors. + the old default links and colors. See |:highlight| for a list of highlight + groups colorschemes should set. - 'autoindent' is enabled - 'autoread' is enabled (works in all UIs, including terminal) @@ -181,7 +182,10 @@ nvim.terminal: when 'background' is "light". While this may not reflect the actual foreground/background color, it permits 'background' to be retained for a nested Nvim instance running in the terminal emulator. -- TermOpen: Sets default options for |terminal| buffers: +- TermRequest: Nvim will create extmarks for shells which + annotate their prompts with OSC 133 escape sequences, enabling users to + quickly navigate between prompts using |[[| and |]]|. +- TermOpen: Sets default options and mappings for |terminal| buffers: - 'nomodifiable' - 'undolevels' set to -1 - 'textwidth' set to 0 @@ -193,6 +197,7 @@ nvim.terminal: - 'foldcolumn' set to "0" - 'winhighlight' uses |hl-StatusLineTerm| and |hl-StatusLineTermNC| in place of |hl-StatusLine| and |hl-StatusLineNC| + - |[[| and |]]| to navigate between shell prompts nvim.cmdwin: - CmdwinEnter: Limits syntax sync to maxlines=1 in the |cmdwin|. diff --git a/runtime/ftplugin/go.vim b/runtime/ftplugin/go.vim index 75f78cfa4b..b83db424bd 100644 --- a/runtime/ftplugin/go.vim +++ b/runtime/ftplugin/go.vim @@ -3,6 +3,7 @@ " Maintainer: David Barnett (https://github.com/google/vim-ft-go) " Last Change: 2014 Aug 16 " 2024 Jul 16 by Vim Project (add recommended indent style) +" 2025 Mar 07 by Vim Project (add formatprg and keywordprg option #16804) if exists('b:did_ftplugin') finish @@ -10,15 +11,39 @@ endif let b:did_ftplugin = 1 setlocal formatoptions-=t +setlocal formatprg=gofmt setlocal comments=s1:/*,mb:*,ex:*/,:// setlocal commentstring=//\ %s +setlocal keywordprg=:GoKeywordPrg -let b:undo_ftplugin = 'setl fo< com< cms<' +command! -buffer -nargs=* GoKeywordPrg call s:GoKeywordPrg() + +let b:undo_ftplugin = 'setl fo< com< cms< fp< kp<' + \ . '| delcommand -buffer GoKeywordPrg' if get(g:, 'go_recommended_style', 1) setlocal noexpandtab softtabstop=0 shiftwidth=0 let b:undo_ftplugin ..= ' | setl et< sts< sw<' endif +if !exists('*' .. expand('<SID>') .. 'GoKeywordPrg') + func! s:GoKeywordPrg() + let temp_isk = &l:iskeyword + setl iskeyword+=. + try + let cmd = 'go doc -C ' . shellescape(expand('%:h')) . ' ' . shellescape(expand('<cword>')) + if has('nvim') + exe "term" cmd + startinsert + tmap <buffer> <Esc> <Cmd>call jobstop(&channel) <Bar> bdelete<CR> + else + exe '!' . cmd + endif + finally + let &l:iskeyword = temp_isk + endtry + endfunc +endif + " vim: sw=2 sts=2 et diff --git a/runtime/ftplugin/tera.vim b/runtime/ftplugin/tera.vim new file mode 100644 index 0000000000..65bae70048 --- /dev/null +++ b/runtime/ftplugin/tera.vim @@ -0,0 +1,30 @@ +" Vim filetype plugin file +" Language: Tera +" Maintainer: Muntasir Mahmud <muntasir.joypurhat@gmail.com> +" Last Change: 2025 Mar 08 + +if exists("b:did_ftplugin") + finish +endif +let b:did_ftplugin = 1 + +setlocal autoindent + +setlocal commentstring={#\ %s\ #} +setlocal comments=s:{#,e:#} + +if exists("loaded_matchit") + let b:match_ignorecase = 0 + let b:match_words = '{#:##\|#},{% *if:{% *else\>:{% *elif\>:{% *endif %},{% *for\>:{% *endfor %},{% *macro\>:{% *endmacro %},{% *block\>:{% *endblock %},{% *filter\>:{% *endfilter %},{% *set\>:{% *endset %},{% *raw\>:{% *endraw %},{% *with\>:{% *endwith %}' +endif + +setlocal includeexpr=substitute(v:fname,'\\([^.]*\\)$','\\1','g') +setlocal suffixesadd=.tera + +setlocal expandtab +setlocal shiftwidth=2 +setlocal softtabstop=2 + +let b:undo_ftplugin = "setlocal autoindent< commentstring< comments< " .. + \ "includeexpr< suffixesadd< expandtab< shiftwidth< softtabstop<" +let b:undo_ftplugin .= "|unlet! b:match_ignorecase b:match_words" diff --git a/runtime/ftplugin/vim.vim b/runtime/ftplugin/vim.vim index 16730185c7..4772683ee8 100644 --- a/runtime/ftplugin/vim.vim +++ b/runtime/ftplugin/vim.vim @@ -1,7 +1,7 @@ " Vim filetype plugin " Language: Vim " Maintainer: Doug Kearns <dougkearns@gmail.com> -" Last Change: 2025 Feb 25 +" Last Change: 2025 Mar 05 " Former Maintainer: Bram Moolenaar <Bram@vim.org> " Contributors: Riley Bruins <ribru17@gmail.com> ('commentstring'), " @Konfekt @@ -85,6 +85,8 @@ if !exists("*" .. expand("<SID>") .. "Help") return ':'.topic elseif pre =~# '\<v:$' return 'v:'.topic + elseif pre =~# '<$' + return '<'.topic.'>' elseif pre =~# '\\$' return '/\'.topic elseif topic ==# 'v' && post =~# ':\w\+' diff --git a/runtime/lua/tohtml.lua b/runtime/lua/tohtml.lua index 4415a8cdca..6b8daab2c5 100644 --- a/runtime/lua/tohtml.lua +++ b/runtime/lua/tohtml.lua @@ -205,7 +205,9 @@ local function try_query_terminal_color(color) once = true, callback = function(args) hex = '#' - .. table.concat({ args.data:match('\027%]%d+;%d*;?rgb:(%w%w)%w%w/(%w%w)%w%w/(%w%w)%w%w') }) + .. table.concat({ + args.data.sequence:match('\027%]%d+;%d*;?rgb:(%w%w)%w%w/(%w%w)%w%w/(%w%w)%w%w'), + }) end, }) if type(color) == 'number' then diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index c2e4e76dd6..c709f0a308 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -515,8 +515,8 @@ do if channel == 0 then return end - local fg_request = args.data == '\027]10;?' - local bg_request = args.data == '\027]11;?' + local fg_request = args.data.sequence == '\027]10;?' + local bg_request = args.data.sequence == '\027]11;?' if fg_request or bg_request then -- WARN: This does not return the actual foreground/background color, -- but rather returns: @@ -534,14 +534,59 @@ do end, }) + local nvim_terminal_prompt_ns = vim.api.nvim_create_namespace('nvim.terminal.prompt') + vim.api.nvim_create_autocmd('TermRequest', { + group = nvim_terminal_augroup, + desc = 'Mark shell prompts indicated by OSC 133 sequences for navigation', + callback = function(args) + if string.match(args.data.sequence, '^\027]133;A') then + local lnum = args.data.cursor[1] ---@type integer + vim.api.nvim_buf_set_extmark(args.buf, nvim_terminal_prompt_ns, lnum - 1, 0, {}) + end + end, + }) + + ---@param ns integer + ---@param buf integer + ---@param count integer + local function jump_to_prompt(ns, win, buf, count) + local row, col = unpack(vim.api.nvim_win_get_cursor(win)) + local start = -1 + local end_ ---@type 0|-1 + if count > 0 then + start = row + end_ = -1 + elseif count < 0 then + -- Subtract 2 because row is 1-based, but extmarks are 0-based + start = row - 2 + end_ = 0 + end + + if start < 0 then + return + end + + local extmarks = vim.api.nvim_buf_get_extmarks( + buf, + ns, + { start, col }, + end_, + { limit = math.abs(count) } + ) + if #extmarks > 0 then + local extmark = extmarks[math.min(#extmarks, math.abs(count))] + vim.api.nvim_win_set_cursor(win, { extmark[2] + 1, extmark[3] }) + end + end + vim.api.nvim_create_autocmd('TermOpen', { group = nvim_terminal_augroup, desc = 'Default settings for :terminal buffers', - callback = function() - vim.bo.modifiable = false - vim.bo.undolevels = -1 - vim.bo.scrollback = vim.o.scrollback < 0 and 10000 or math.max(1, vim.o.scrollback) - vim.bo.textwidth = 0 + callback = function(args) + vim.bo[args.buf].modifiable = false + vim.bo[args.buf].undolevels = -1 + vim.bo[args.buf].scrollback = vim.o.scrollback < 0 and 10000 or math.max(1, vim.o.scrollback) + vim.bo[args.buf].textwidth = 0 vim.wo[0][0].wrap = false vim.wo[0][0].list = false vim.wo[0][0].number = false @@ -555,6 +600,13 @@ do winhl = winhl .. ',' end vim.wo[0][0].winhighlight = winhl .. 'StatusLine:StatusLineTerm,StatusLineNC:StatusLineTermNC' + + vim.keymap.set('n', '[[', function() + jump_to_prompt(nvim_terminal_prompt_ns, 0, args.buf, -vim.v.count1) + end, { buffer = args.buf, desc = 'Jump [count] shell prompts backward' }) + vim.keymap.set('n', ']]', function() + jump_to_prompt(nvim_terminal_prompt_ns, 0, args.buf, vim.v.count1) + end, { buffer = args.buf, desc = 'Jump [count] shell prompts forward' }) end, }) @@ -712,7 +764,7 @@ do nested = true, desc = "Update the value of 'background' automatically based on the terminal emulator's background color", callback = function(args) - local resp = args.data ---@type string + local resp = args.data.sequence ---@type string local r, g, b = parseosc11(resp) if r and g and b then local rr = parsecolor(r) @@ -788,7 +840,7 @@ do group = group, nested = true, callback = function(args) - local resp = args.data ---@type string + local resp = args.data.sequence ---@type string local decrqss = resp:match('^\027P1%$r([%d;:]+)m$') if decrqss then @@ -834,9 +886,7 @@ do -- terminal responds to the DECRQSS with the same SGR sequence that we -- sent then the terminal supports truecolor. local decrqss = '\027P$qm\027\\' - if os.getenv('TMUX') then - decrqss = string.format('\027Ptmux;%s\027\\', decrqss:gsub('\027', '\027\027')) - end + -- Reset attributes first, as other code may have set attributes. io.stdout:write(string.format('\027[0m\027[48;2;%d;%d;%dm%s', r, g, b, decrqss)) diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 975f3fea4a..e29d8f1c30 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -1142,6 +1142,21 @@ do end end +--- @param inspect_strings boolean use vim.inspect() for strings +function vim._print(inspect_strings, ...) + local msg = {} + for i = 1, select('#', ...) do + local o = select(i, ...) + if not inspect_strings and type(o) == 'string' then + table.insert(msg, o) + else + table.insert(msg, vim.inspect(o, { newline = '\n', indent = ' ' })) + end + end + print(table.concat(msg, '\n')) + return ... +end + --- "Pretty prints" the given arguments and returns them unmodified. --- --- Example: @@ -1155,17 +1170,7 @@ end --- @param ... any --- @return any # given arguments. function vim.print(...) - local msg = {} - for i = 1, select('#', ...) do - local o = select(i, ...) - if type(o) == 'string' then - table.insert(msg, o) - else - table.insert(msg, vim.inspect(o, { newline = '\n', indent = ' ' })) - end - end - print(table.concat(msg, '\n')) - return ... + return vim._print(false, ...) end --- Translates keycodes. diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 15cdd8fc79..b890b64174 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -9,19 +9,16 @@ error('Cannot require a meta file') vim.api = {} ---- @private --- @param buffer integer --- @param keys boolean --- @param dot boolean --- @return string function vim.api.nvim__buf_debug_extmarks(buffer, keys, dot) end ---- @private --- @param buffer integer --- @return table<string,any> function vim.api.nvim__buf_stats(buffer) end ---- @private --- EXPERIMENTAL: this API may change in the future. --- --- Sets info for the completion item at the given index. If the info text was shown in a window, @@ -35,11 +32,9 @@ function vim.api.nvim__buf_stats(buffer) end --- - bufnr: (number) buffer id in floating window function vim.api.nvim__complete_set(index, opts) end ---- @private --- @return string function vim.api.nvim__get_lib_dir() end ---- @private --- Find files in runtime directories --- --- @param pat any[] pattern of files to search for @@ -48,7 +43,6 @@ function vim.api.nvim__get_lib_dir() end --- @return string[] # list of absolute paths to the found files function vim.api.nvim__get_runtime(pat, all, opts) end ---- @private --- Returns object given as argument. --- --- This API function is used for testing. One should not rely on its presence @@ -58,7 +52,6 @@ function vim.api.nvim__get_runtime(pat, all, opts) end --- @return any # its argument. function vim.api.nvim__id(obj) end ---- @private --- Returns array given as argument. --- --- This API function is used for testing. One should not rely on its presence @@ -68,7 +61,6 @@ function vim.api.nvim__id(obj) end --- @return any[] # its argument. function vim.api.nvim__id_array(arr) end ---- @private --- Returns dict given as argument. --- --- This API function is used for testing. One should not rely on its presence @@ -78,7 +70,6 @@ function vim.api.nvim__id_array(arr) end --- @return table<string,any> # its argument. function vim.api.nvim__id_dict(dct) end ---- @private --- Returns floating-point value given as argument. --- --- This API function is used for testing. One should not rely on its presence @@ -88,7 +79,6 @@ function vim.api.nvim__id_dict(dct) end --- @return number # its argument. function vim.api.nvim__id_float(flt) end ---- @private --- NB: if your UI doesn't use hlstate, this will not return hlstate first time. --- @param grid integer --- @param row integer @@ -96,12 +86,10 @@ function vim.api.nvim__id_float(flt) end --- @return any[] function vim.api.nvim__inspect_cell(grid, row, col) end ---- @private --- For testing. The condition in schar_cache_clear_if_full is hard to --- reach, so this function can be used to force a cache clear in a test. function vim.api.nvim__invalidate_glyph_cache() end ---- @private --- EXPERIMENTAL: this API will change in the future. --- --- Get the properties for namespace @@ -110,7 +98,6 @@ function vim.api.nvim__invalidate_glyph_cache() end --- @return vim.api.keyset.ns_opts # Map defining the namespace properties, see |nvim__ns_set()| function vim.api.nvim__ns_get(ns_id) end ---- @private --- EXPERIMENTAL: this API will change in the future. --- --- Set some properties for namespace @@ -120,7 +107,6 @@ function vim.api.nvim__ns_get(ns_id) end --- - wins: a list of windows to be scoped in function vim.api.nvim__ns_set(ns_id, opts) end ---- @private --- EXPERIMENTAL: this API may change in the future. --- --- Instruct Nvim to redraw various components. @@ -148,21 +134,17 @@ function vim.api.nvim__ns_set(ns_id, opts) end --- - tabline: Redraw the 'tabline'. function vim.api.nvim__redraw(opts) end ---- @private --- @return any[] function vim.api.nvim__runtime_inspect() end ---- @private --- @param path string function vim.api.nvim__screenshot(path) end ---- @private --- Gets internal stats. --- --- @return table<string,any> # Map of various internal stats. function vim.api.nvim__stats() end ---- @private --- @param str string --- @return any function vim.api.nvim__unpack(str) end diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 8df174a838..59e65b0585 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -1083,15 +1083,15 @@ vim.go.cia = vim.go.completeitemalign --- match, e.g., what file it comes from. --- --- noinsert Do not insert any text for a match until the user selects ---- a match from the menu. Only works in combination with +--- a match from the menu. Only works in combination with --- "menu" or "menuone". No effect if "longest" is present. --- --- noselect Same as "noinsert", except that no menu item is ---- pre-selected. If both "noinsert" and "noselect" are +--- pre-selected. If both "noinsert" and "noselect" are --- present, "noselect" has precedence. --- --- nosort Disable sorting of completion candidates based on fuzzy ---- scores when "fuzzy" is enabled. Candidates will appear +--- scores when "fuzzy" is enabled. Candidates will appear --- in their original order. --- --- popup Show extra information about the currently selected @@ -1102,7 +1102,7 @@ vim.go.cia = vim.go.completeitemalign --- Preinsert the portion of the first candidate word that is --- not part of the current completion leader and using the --- `hl-ComplMatchIns` highlight group. In order for it to ---- work, "fuzzy" must not bet set and "menuone" must be set. +--- work, "fuzzy" must not be set and "menuone" must be set. --- --- preview Show extra information about the currently selected --- completion in the preview window. Only works in @@ -4640,7 +4640,7 @@ vim.go.pm = vim.go.patchmode --- ``` --- - A directory name may end in a ':' or '/'. --- - Environment variables are expanded `:set_env`. ---- - When using `netrw.vim` URLs can be used. For example, adding +--- - When using `netrw` URLs can be used. For example, adding --- "https://www.vim.org" will make ":find index.html" work. --- - Search upwards and downwards in a directory tree using "*", "**" and --- ";". See `file-searching` for info and syntax. @@ -7691,7 +7691,10 @@ vim.go.wmnu = vim.go.wildmenu --- "lastused" When completing buffer names and more than one buffer --- matches, sort buffers by time last used (other than --- the current buffer). ---- When there is only a single match, it is fully completed in all cases. +--- "noselect" Do not pre-select first menu item and start 'wildmenu' +--- if it is enabled. +--- When there is only a single match, it is fully completed in all cases +--- except when "noselect" is present. --- --- Examples of useful colon-separated values: --- "longest:full" Like "longest", but also start 'wildmenu' if it is @@ -7729,7 +7732,17 @@ vim.go.wmnu = vim.go.wildmenu --- ```vim --- set wildmode=longest,list --- ``` ---- Complete longest common string, then list alternatives. +--- Complete longest common string, then list alternatives +--- +--- ```vim +--- set wildmode=noselect:full +--- ``` +--- Display 'wildmenu' without completing, then each full match +--- +--- ```vim +--- set wildmode=noselect:lastused,full +--- ``` +--- Same as above, but sort buffers by time last used. --- More info here: `cmdline-completion`. --- --- @type string diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 26f0011e82..4749f94b1b 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -1757,8 +1757,7 @@ local function render_virtual_lines(namespace, bufnr, diagnostics) string.rep( ' ', -- +1 because indexing starts at 0 in one API but at 1 in the other. - -- -1 for non-first lines, since the previous column was already drawn. - distance_between_cols(bufnr, diag.lnum, prev_col + 1, diag.col) - 1 + distance_between_cols(bufnr, diag.lnum, prev_col + 1, diag.col) ), }) else diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index c47ce5e761..2c058bc6bf 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -198,6 +198,7 @@ local extension = { abap = 'abap', abc = 'abc', abl = 'abel', + abnf = 'abnf', wrm = 'acedb', ads = 'ada', ada = 'ada', @@ -1206,6 +1207,7 @@ local extension = { tl = 'teal', templ = 'templ', tmpl = 'template', + tera = 'tera', ti = 'terminfo', dtx = 'tex', ltx = 'tex', diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 47f41b43aa..2da591e9bb 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -1129,6 +1129,7 @@ local function on_code_action_results(results, opts) if not choice then return end + -- textDocument/codeAction can return either Command[] or CodeAction[] -- -- CodeAction @@ -1140,15 +1141,22 @@ local function on_code_action_results(results, opts) -- title: string -- command: string -- arguments?: any[] - -- + local client = assert(lsp.get_client_by_id(choice.ctx.client_id)) local action = choice.action local bufnr = assert(choice.ctx.bufnr, 'Must have buffer number') - if not action.edit and client:supports_method(ms.codeAction_resolve) then + -- Only code actions are resolved, so if we have a command, just apply it. + if type(action.title) == 'string' and type(action.command) == 'string' then + apply_action(action, client, choice.ctx) + return + end + + if not action.edit or not action.command and client:supports_method(ms.codeAction_resolve) then client:request(ms.codeAction_resolve, action, function(err, resolved_action) if err then - if action.command then + -- If resolve fails, try to apply the edit/command from the original code action. + if action.edit or action.command then apply_action(action, client, choice.ctx) else vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR) diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 7975006f9d..feff9adbd0 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -424,7 +424,7 @@ function protocol.make_client_capabilities() isPreferredSupport = true, dataSupport = true, resolveSupport = { - properties = { 'edit' }, + properties = { 'edit', 'command' }, }, }, codeLens = { diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 1219f71427..ef177e903f 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1656,7 +1656,9 @@ function M.open_floating_preview(contents, syntax, opts) if not opts.height then -- Reduce window height if TS highlighter conceals code block backticks. local conceal_height = api.nvim_win_text_height(floating_winnr, {}).all - api.nvim_win_set_height(floating_winnr, conceal_height) + if conceal_height < api.nvim_win_get_height(floating_winnr) then + api.nvim_win_set_height(floating_winnr, conceal_height) + end end end diff --git a/runtime/lua/vim/termcap.lua b/runtime/lua/vim/termcap.lua index 4aa41bba9b..2789aacb90 100644 --- a/runtime/lua/vim/termcap.lua +++ b/runtime/lua/vim/termcap.lua @@ -34,7 +34,7 @@ function M.query(caps, cb) local id = vim.api.nvim_create_autocmd('TermResponse', { nested = true, callback = function(args) - local resp = args.data ---@type string + local resp = args.data.sequence ---@type string local k, rest = resp:match('^\027P1%+r(%x+)(.*)$') if k and rest then local cap = vim.text.hexdecode(k) @@ -71,11 +71,6 @@ function M.query(caps, cb) local query = string.format('\027P+q%s\027\\', table.concat(encoded, ';')) - -- If running in tmux, wrap with the passthrough sequence - if os.getenv('TMUX') then - query = string.format('\027Ptmux;%s\027\\', query:gsub('\027', '\027\027')) - end - io.stdout:write(query) timer:start(1000, 0, function() diff --git a/runtime/lua/vim/ui/clipboard/osc52.lua b/runtime/lua/vim/ui/clipboard/osc52.lua index 50afbe63a5..73f64c9743 100644 --- a/runtime/lua/vim/ui/clipboard/osc52.lua +++ b/runtime/lua/vim/ui/clipboard/osc52.lua @@ -25,7 +25,7 @@ function M.paste(reg) local contents = nil local id = vim.api.nvim_create_autocmd('TermResponse', { callback = function(args) - local resp = args.data ---@type string + local resp = args.data.sequence ---@type string local encoded = resp:match('\027%]52;%w?;([A-Za-z0-9+/=]*)') if encoded then contents = vim.base64.decode(encoded) diff --git a/runtime/makemenu.vim b/runtime/makemenu.vim index 01f214cfa0..3d27bfc551 100644 --- a/runtime/makemenu.vim +++ b/runtime/makemenu.vim @@ -1,6 +1,6 @@ " Script to define the syntax menu in synmenu.vim " Maintainer: The Vim Project <https://github.com/vim/vim> -" Last Change: 2023 Aug 10 +" Last Change: 2025 Mar 09 " Former Maintainer: Bram Moolenaar <Bram@vim.org> " This is used by "make menu" in the src directory. @@ -602,6 +602,7 @@ SynMenu T.Tcl/Tk:tcl SynMenu T.TealInfo:tli SynMenu T.Telix\ Salt:tsalt SynMenu T.Termcap/Printcap:ptcap +SynMenu T.Tera:tera SynMenu T.Terminfo:terminfo SynMenu T.Tera\ Term:teraterm SynMenu T.TeX.TeX/LaTeX:tex diff --git a/runtime/plugin/matchparen.vim b/runtime/plugin/matchparen.vim index 13d1b6824f..079cc668f0 100644 --- a/runtime/plugin/matchparen.vim +++ b/runtime/plugin/matchparen.vim @@ -1,6 +1,6 @@ " Vim plugin for showing matching parens " Maintainer: The Vim Project <https://github.com/vim/vim> -" Last Change: 2024 May 18 +" Last Change: 2025 Mar 08 " Former Maintainer: Bram Moolenaar <Bram@vim.org> " Exit quickly when: @@ -114,6 +114,17 @@ func s:Highlight_Matching_Pair() \ .. 'string\|character\|singlequote\|escape\|symbol\|comment' \ .. "') != -1" else + " do not attempt to match when the syntax item where the cursor is + " indicates there does not exist a matching parenthesis, e.g. for shells + " case statement: "case $var in foobar)" + " + " add the check behind a filetype check, so it only needs to be + " evaluated for certain filetypes + if ['sh']->index(&filetype) >= 0 && + \ synstack(".", col("."))->indexof({_, id -> synIDattr(id, "name") + \ =~? "shSnglCase"}) >= 0 + return + endif " Build an expression that detects whether the current cursor position is " in certain syntax types (string, comment, etc.), for use as " searchpairpos()'s skip argument. diff --git a/runtime/synmenu.vim b/runtime/synmenu.vim index edc6eabd1d..6a3bb8eaf4 100644 --- a/runtime/synmenu.vim +++ b/runtime/synmenu.vim @@ -582,26 +582,27 @@ an 50.150.190 &Syntax.T.TealInfo :cal SetSyn("tli")<CR> an 50.150.200 &Syntax.T.Telix\ Salt :cal SetSyn("tsalt")<CR> an 50.150.210 &Syntax.T.Termcap/Printcap :cal SetSyn("ptcap")<CR> an 50.150.220 &Syntax.T.Terminfo :cal SetSyn("terminfo")<CR> -an 50.150.230 &Syntax.T.Tera\ Term :cal SetSyn("teraterm")<CR> -an 50.150.240 &Syntax.T.TeX.TeX/LaTeX :cal SetSyn("tex")<CR> -an 50.150.250 &Syntax.T.TeX.plain\ TeX :cal SetSyn("plaintex")<CR> -an 50.150.260 &Syntax.T.TeX.Initex :cal SetSyn("initex")<CR> -an 50.150.270 &Syntax.T.TeX.ConTeXt :cal SetSyn("context")<CR> -an 50.150.280 &Syntax.T.TeX.TeX\ configuration :cal SetSyn("texmf")<CR> -an 50.150.290 &Syntax.T.TeX.Texinfo :cal SetSyn("texinfo")<CR> -an 50.150.300 &Syntax.T.TF\ mud\ client :cal SetSyn("tf")<CR> -an 50.150.310 &Syntax.T.Tidy\ configuration :cal SetSyn("tidy")<CR> -an 50.150.320 &Syntax.T.Tilde :cal SetSyn("tilde")<CR> -an 50.150.330 &Syntax.T.Tmux\ configuration :cal SetSyn("tmux")<CR> -an 50.150.340 &Syntax.T.TPP :cal SetSyn("tpp")<CR> -an 50.150.350 &Syntax.T.Trasys\ input :cal SetSyn("trasys")<CR> -an 50.150.360 &Syntax.T.Treetop :cal SetSyn("treetop")<CR> -an 50.150.370 &Syntax.T.Trustees :cal SetSyn("trustees")<CR> -an 50.150.380 &Syntax.T.TSS.Command\ Line :cal SetSyn("tsscl")<CR> -an 50.150.390 &Syntax.T.TSS.Geometry :cal SetSyn("tssgm")<CR> -an 50.150.400 &Syntax.T.TSS.Optics :cal SetSyn("tssop")<CR> -an 50.150.410 &Syntax.T.Typescript :cal SetSyn("typescript")<CR> -an 50.150.420 &Syntax.T.TypescriptReact :cal SetSyn("typescriptreact")<CR> +an 50.150.230 &Syntax.T.Tera :cal SetSyn("tera")<CR> +an 50.150.240 &Syntax.T.Tera\ Term :cal SetSyn("teraterm")<CR> +an 50.150.250 &Syntax.T.TeX.TeX/LaTeX :cal SetSyn("tex")<CR> +an 50.150.260 &Syntax.T.TeX.plain\ TeX :cal SetSyn("plaintex")<CR> +an 50.150.270 &Syntax.T.TeX.Initex :cal SetSyn("initex")<CR> +an 50.150.280 &Syntax.T.TeX.ConTeXt :cal SetSyn("context")<CR> +an 50.150.290 &Syntax.T.TeX.TeX\ configuration :cal SetSyn("texmf")<CR> +an 50.150.300 &Syntax.T.TeX.Texinfo :cal SetSyn("texinfo")<CR> +an 50.150.310 &Syntax.T.TF\ mud\ client :cal SetSyn("tf")<CR> +an 50.150.320 &Syntax.T.Tidy\ configuration :cal SetSyn("tidy")<CR> +an 50.150.330 &Syntax.T.Tilde :cal SetSyn("tilde")<CR> +an 50.150.340 &Syntax.T.Tmux\ configuration :cal SetSyn("tmux")<CR> +an 50.150.350 &Syntax.T.TPP :cal SetSyn("tpp")<CR> +an 50.150.360 &Syntax.T.Trasys\ input :cal SetSyn("trasys")<CR> +an 50.150.370 &Syntax.T.Treetop :cal SetSyn("treetop")<CR> +an 50.150.380 &Syntax.T.Trustees :cal SetSyn("trustees")<CR> +an 50.150.390 &Syntax.T.TSS.Command\ Line :cal SetSyn("tsscl")<CR> +an 50.150.400 &Syntax.T.TSS.Geometry :cal SetSyn("tssgm")<CR> +an 50.150.410 &Syntax.T.TSS.Optics :cal SetSyn("tssop")<CR> +an 50.150.420 &Syntax.T.Typescript :cal SetSyn("typescript")<CR> +an 50.150.430 &Syntax.T.TypescriptReact :cal SetSyn("typescriptreact")<CR> an 50.160.100 &Syntax.UV.Udev\ config :cal SetSyn("udevconf")<CR> an 50.160.110 &Syntax.UV.Udev\ permissions :cal SetSyn("udevperm")<CR> an 50.160.120 &Syntax.UV.Udev\ rules :cal SetSyn("udevrules")<CR> diff --git a/runtime/syntax/abnf.vim b/runtime/syntax/abnf.vim new file mode 100644 index 0000000000..13459eb9f8 --- /dev/null +++ b/runtime/syntax/abnf.vim @@ -0,0 +1,33 @@ +" Vim compiler file +" Language: abnf +" Maintainer: A4-Tacks <wdsjxhno1001@163.com> +" Last Change: 2025 Mar 05 +" Upstream: https://github.com/A4-Tacks/abnf.vim + +" Implementing RFC-5234, RFC-7405 + +if exists('b:current_syntax') + finish +endif + +syn case ignore + +syn match abnfError /[<>"]/ +syn match abnfComment /;.*/ +syn match abnfOption /[[/\]]/ +syn region abnfString start=/\(%[si]\)\="/ end=/"/ oneline +syn region abnfProse start=/</ end=/>/ oneline +syn match abnfNumVal /\v\%b[01]+%(%(\.[01]+)+|-[01]+)=>/ +syn match abnfNumVal /\v\%d\d+%(%(\.\d+)+|-\d+)=>/ +syn match abnfNumVal /\v\%x[0-9a-f]+%(%(\.[0-9a-f]+)+|-[0-9a-f]+)=>/ +syn match abnfRepeat /\v%(%(<\d+)=\*\d*|<\d+ =)\ze[^ \t\r\n0-9*/)\]]/ + +hi def link abnfError Error +hi def link abnfComment Comment +hi def link abnfOption PreProc +hi def link abnfString String +hi def link abnfProse String +hi def link abnfNumVal Number +hi def link abnfRepeat Repeat + +" vim:noet:ts=8:sts=8:nowrap diff --git a/runtime/syntax/tera.vim b/runtime/syntax/tera.vim new file mode 100644 index 0000000000..922b9c9752 --- /dev/null +++ b/runtime/syntax/tera.vim @@ -0,0 +1,96 @@ +" Vim syntax file +" Language: Tera +" Maintainer: Muntasir Mahmud <muntasir.joypurhat@gmail.com> +" Last Change: 2025 Mar 09 + +if exists("b:current_syntax") + finish +endif + +" Detect the underlying language based on filename pattern +" For files like file.html.tera, we want to load html syntax +let s:filename = expand("%:t") +let s:dotpos = strridx(s:filename, '.', strridx(s:filename, '.tera') - 1) +let s:underlying_filetype = "" + +if s:dotpos != -1 + let s:underlying_ext = s:filename[s:dotpos+1:strridx(s:filename, '.tera')-1] + if s:underlying_ext != "" && s:underlying_ext != "tera" + let s:underlying_filetype = s:underlying_ext + endif +endif + +" Load the underlying language syntax if detected +if s:underlying_filetype != "" + execute "runtime! syntax/" . s:underlying_filetype . ".vim" + unlet! b:current_syntax +else + " Default to HTML if no specific language detected + runtime! syntax/html.vim + unlet! b:current_syntax +endif + +" Tera comment blocks: {# comment #} +syn region teraCommentBlock start="{#" end="#}" contains=@Spell containedin=cssDefinition,cssStyle,htmlHead,htmlTitle + +" Tera statements: {% if condition %} +syn region teraStatement start="{%" end="%}" contains=teraKeyword,teraString,teraNumber,teraFunction,teraBoolean,teraFilter,teraOperator containedin=cssDefinition,cssStyle,htmlHead,htmlTitle + +" Tera expressions: {{ variable }} +syn region teraExpression start="{{" end="}}" contains=teraString,teraNumber,teraFunction,teraBoolean,teraFilter,teraOperator,teraIdentifier containedin=cssDefinition,cssStyle,htmlHead,htmlTitle + +" Special handling for raw blocks - content inside shouldn't be processed +syn region teraRawBlock start="{% raw %}" end="{% endraw %}" contains=TOP,teraCommentBlock,teraStatement,teraExpression + +" Control structure keywords +syn keyword teraKeyword contained if else elif endif for endfor in macro endmacro +syn keyword teraKeyword contained block endblock extends include import set endset +syn keyword teraKeyword contained break continue filter endfilter raw endraw with endwith + +" Identifiers - define before operators for correct priority +syn match teraIdentifier contained "\<\w\+\>" + +" Operators used in expressions and statements +syn match teraOperator contained "==\|!=\|>=\|<=\|>\|<\|+\|-\|*\|/" +syn match teraOperator contained "{\@<!%}\@!" " Match % but not when part of {% or %} +syn keyword teraOperator contained and or not is as + +" Functions and filters +syn match teraFunction contained "\<\w\+\ze(" +syn match teraFilter contained "|\_s*\w\+" + +" String literals - both single and double quoted +syn region teraString contained start=+"+ skip=+\\"+ end=+"+ contains=@Spell +syn region teraString contained start=+'+ skip=+\\'+ end=+'+ contains=@Spell + +" Numeric literals - both integer and float +syn match teraNumber contained "\<\d\+\>" +syn match teraNumber contained "\<\d\+\.\d\+\>" + +" Boolean values +syn keyword teraBoolean contained true false + +" Highlighting links +hi def link teraCommentBlock Comment +hi def link teraKeyword Statement +hi def link teraOperator Operator +hi def link teraFunction Function +hi def link teraIdentifier Identifier +hi def link teraString String +hi def link teraNumber Number +hi def link teraBoolean Boolean +hi def link teraFilter PreProc + +" Special highlighting for blocks and expressions +hi def link teraStatement PreProc +hi def link teraExpression PreProc + +" Clean up script-local variables +unlet s:filename +unlet s:dotpos +if exists("s:underlying_ext") + unlet s:underlying_ext +endif +unlet s:underlying_filetype + +let b:current_syntax = "tera" diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index de0a1228e2..7fe22190b7 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -711,9 +711,9 @@ syn match vimCmplxRepeat '[^a-zA-Z_/\\()]q[0-9a-zA-Z"]\>'lc=1 syn match vimCmplxRepeat '@[0-9a-z".=@:]\ze\($\|[^a-zA-Z]\>\)' " Set command and associated set-options (vimOptions) with comment {{{2 -syn match vimSet "\<\%(setl\%[ocal]\|setg\%[lobal]\|se\%[t]\)\>" skipwhite nextgroup=vimSetBang,vimSetRegion -syn region vimSetRegion contained start="\S" skip=+\\\\\|\\|\|\n\s*\\\|\n\s*["#]\\ + matchgroup=vimCmdSep end="|" end="$" matchgroup=vimNotation end="<[cC][rR]>" keepend contains=@vimComment,@vimContinue,vimErrSetting,vimOption,vimSetAll,vimSetTermcap -syn region vimSetEqual contained matchgroup=vimOper start="[=:]\|[-+^]=" skip=+\\\\\|\\|\|\\\s\|\n\s*\\\|\n\s*["#]\\ \|^\s*\\\|^\s*["#]\\ + matchgroup=vimCmdSep end="|" end="\ze\s" end="$" contains=@vimContinue,vimCtrlChar,vimEnvvar,vimNotation,vimSetSep +syn match vimSet "\<\%(setl\%[ocal]\|setg\%[lobal]\|se\%[t]\)\>" skipwhite nextgroup=vimSetBang,vimSetArgs +syn region vimSetArgs contained start="\S" skip=+\\|\|\n\s*\\\|\n\s*["#]\\ + matchgroup=vimCmdSep end="|" end="$" matchgroup=vimNotation end="<[cC][rR]>" keepend contains=@vimComment,@vimContinue,vimErrSetting,vimOption,vimSetAll,vimSetTermcap +syn region vimSetEqual contained matchgroup=vimOper start="[=:]\|[-+^]=" skip=+\\|\|\\\s\|\n\s*\\\|\n\s*["#]\\ \|^\s*\\\|^\s*["#]\\ + matchgroup=vimCmdSep end="|" end="\ze\s" end="$" contains=@vimContinue,vimCtrlChar,vimEnvvar,vimNotation,vimSetSep syn match vimSetBang contained "\a\@1<=!" skipwhite nextgroup=vimSetAll,vimSetTermcap syn keyword vimSetAll contained all nextgroup=vimSetMod syn keyword vimSetTermcap contained termcap @@ -835,15 +835,17 @@ syn keyword vimMap mapc[lear] skipwhite nextgroup=vimMapBang,vimMapMod syn keyword vimUnmap cu[nmap] iu[nmap] lu[nmap] nun[map] ou[nmap] sunm[ap] tunma[p] vu[nmap] xu[nmap] skipwhite nextgroup=vimMapMod,vimMapLhs syn keyword vimUnmap unm[ap] skipwhite nextgroup=vimMapBang,vimMapMod,vimMapLhs -syn match vimMapLhs contained "\%(.\|\S\)\+" contains=vimCtrlChar,vimNotation skipwhite nextgroup=vimMapRhs -syn match vimMapLhs contained "\%(.\|\S\)\+\ze\s*$" contains=vimCtrlChar,vimNotation skipwhite skipnl nextgroup=vimMapRhsContinue +syn match vimMapLhs contained "\%(.\|\S\)\+" contains=vimCtrlChar,vimNotation,vimMapLeader skipwhite nextgroup=vimMapRhs +syn match vimMapLhs contained "\%(.\|\S\)\+\ze\s*$" contains=vimCtrlChar,vimNotation,vimMapLeader skipwhite skipnl nextgroup=vimMapRhsContinue syn match vimMapBang contained "\a\@1<=!" skipwhite nextgroup=vimMapMod,vimMapLhs -syn match vimMapMod contained "\%#=1\c<\(buffer\|expr\|\(local\)\=leader\|nowait\|plug\|script\|sid\|unique\|silent\)\+>" contains=vimMapModKey,vimMapModErr skipwhite nextgroup=vimMapMod,vimMapLhs -syn region vimMapRhs contained start="\S" skip=+\\|\|\@1<=|\|\n\s*\\\|\n\s*"\\ + end="|" end="$" contains=@vimContinue,vimCtrlChar,vimNotation skipnl nextgroup=vimMapRhsContinue +syn match vimMapMod contained "\%#=1<\%(buffer\|expr\|nowait\|script\|silent\|special\|unique\)\+>" contains=vimMapModKey,vimMapModErr skipwhite nextgroup=vimMapMod,vimMapLhs +syn region vimMapRhs contained start="\S" skip=+\\|\|\@1<=|\|\n\s*\\\|\n\s*"\\ + end="|" end="$" contains=@vimContinue,vimCtrlChar,vimNotation,vimMapLeader skipnl nextgroup=vimMapRhsContinue " assume a continuation comment introduces the RHS -syn region vimMapRhsContinue contained start=+^\s*\%(\\\|"\\ \)+ skip=+\\|\|\@1<=|\|\n\s*\\\|\n\s*"\\ + end="|" end="$" contains=@vimContinue,vimCtrlChar,vimNotation +syn region vimMapRhsContinue contained start=+^\s*\%(\\\|"\\ \)+ skip=+\\|\|\@1<=|\|\n\s*\\\|\n\s*"\\ + end="|" end="$" contains=@vimContinue,vimCtrlChar,vimNotation,vimMapLeader +syn match vimMapLeader contained "\%#=1\c<\%(local\)\=leader>" contains=vimMapLeaderKey +syn keyword vimMapModKey contained buffer expr nowait script silent special unique syn case ignore -syn keyword vimMapModKey contained buffer expr leader localleader nowait plug script sid silent unique +syn keyword vimMapLeaderKey contained leader localleader syn case match " Menus: {{{2 @@ -909,7 +911,7 @@ syn match vimUsrCmd '^\s*\zs\u\%(\w*\)\@>\%([(#[]\|\s\+\%([-+*/%]\=\|\.\.\)=\)\@ " Vim user commands " Compiler plugins -syn match vimCompilerSet "\<CompilerSet\>" skipwhite nextgroup=vimSetRegion +syn match vimCompilerSet "\<CompilerSet\>" skipwhite nextgroup=vimSetArgs " runtime/makemenu.vim syn match vimSynMenu "\<SynMenu\>" skipwhite nextgroup=vimSynMenuPath @@ -1494,6 +1496,8 @@ if !exists("skip_vim_syntax_inits") hi def link vimLetRegister Special hi def link vimLineComment vimComment hi def link vimMapBang vimBang + hi def link vimMapLeader vimBracket + hi def link vimMapLeaderKey vimNotation hi def link vimMapModKey vimFuncSID hi def link vimMapMod vimBracket hi def link vimMap vimCommand diff --git a/scripts/lintdoc.lua b/scripts/lintdoc.lua index 78cf9fed72..d246c51176 100755 --- a/scripts/lintdoc.lua +++ b/scripts/lintdoc.lua @@ -10,8 +10,10 @@ print('Running lintdoc ...') --- gen_help_html requires :helptags to be generated on $VIMRUNTIME/doc --- :helptags checks for duplicate tags. +-- gen_help_html.lua requires helptags to be generated in $VIMRUNTIME/doc. +-- :helptags also checks for duplicate tags. +-- 🤢 Load netrw so its tags are generated by :helptags. +vim.cmd [[ packadd netrw ]] vim.cmd [[ helptags ALL ]] require('src.gen.gen_help_html').run_validate() diff --git a/src/gen/gen_eval_files.lua b/src/gen/gen_eval_files.lua index f99f6bafbd..b5351cb3a4 100755 --- a/src/gen/gen_eval_files.lua +++ b/src/gen/gen_eval_files.lua @@ -371,10 +371,6 @@ end local function render_api_meta(_f, fun, write) write('') - if vim.startswith(fun.name, 'nvim__') then - write('--- @private') - end - if fun.deprecated then write('--- @deprecated') end diff --git a/src/gen/gen_events.lua b/src/gen/gen_events.lua index 77f766bb28..020599301c 100644 --- a/src/gen/gen_events.lua +++ b/src/gen/gen_events.lua @@ -3,9 +3,14 @@ local names_file = arg[2] local auevents = require('nvim.auevents') local events = auevents.events +local aliases = auevents.aliases -local enum_tgt = io.open(fileio_enum_file, 'w') -local names_tgt = io.open(names_file, 'w') +--- @type string[] +local names = vim.tbl_keys(vim.tbl_extend('error', events, aliases)) +table.sort(names) + +local enum_tgt = assert(io.open(fileio_enum_file, 'w')) +local names_tgt = assert(io.open(names_file, 'w')) enum_tgt:write([[ // IWYU pragma: private, include "nvim/autocmd_defs.h" @@ -18,23 +23,25 @@ static const struct event_name { int event; } event_names[] = {]]) -local aliases = 0 -for i, event in ipairs(events) do - enum_tgt:write(('\n EVENT_%s = %u,'):format(event[1]:upper(), i + aliases - 1)) +for i, name in ipairs(names) do + enum_tgt:write(('\n EVENT_%s = %u,'):format(name:upper(), i - 1)) + local pref_name = aliases[name] ~= nil and aliases[name] or name + local win_local = events[pref_name] + assert(win_local ~= nil) -- Events with positive keys aren't allowed in 'eventignorewin'. - local event_int = ('%sEVENT_%s'):format(event[3] and '-' or '', event[1]:upper()) - names_tgt:write(('\n {%u, "%s", %s},'):format(#event[1], event[1], event_int)) - for _, alias in ipairs(event[2]) do - aliases = aliases + 1 - names_tgt:write(('\n {%u, "%s", %s},'):format(#alias, alias, event_int)) - enum_tgt:write(('\n EVENT_%s = %u,'):format(alias:upper(), i + aliases - 1)) - end - if i == #events then -- Last item. - enum_tgt:write(('\n NUM_EVENTS = %u,'):format(i + aliases)) - end + names_tgt:write( + ('\n [EVENT_%s] = {%u, "%s", %sEVENT_%s},'):format( + name:upper(), + #name, + name, + win_local and '-' or '', + pref_name:upper() + ) + ) end -names_tgt:write('\n {0, NULL, (event_T)0},\n};\n') +enum_tgt:write(('\n NUM_EVENTS = %u,'):format(#names)) +names_tgt:write('\n [NUM_EVENTS] = {0, NULL, (event_T)0},\n};\n') names_tgt:write('\nstatic AutoCmdVec autocmds[NUM_EVENTS] = { 0 };\n') names_tgt:close() diff --git a/src/gen/gen_help_html.lua b/src/gen/gen_help_html.lua index d4766d84a1..0d98d9e1b1 100644 --- a/src/gen/gen_help_html.lua +++ b/src/gen/gen_help_html.lua @@ -778,6 +778,7 @@ local function parse_buf(fname, text, parser_path) vim.treesitter.language.add('vimdoc', { path = parser_path }) end local lang_tree = assert(vim.treesitter.get_parser(buf, nil, { error = false })) + lang_tree:parse() return lang_tree, buf end @@ -1397,6 +1398,9 @@ function M.validate(help_dir, include, parser_path) local files_to_errors = {} ---@type table<string, string[]> ensure_runtimepath() tagmap = get_helptags(vim.fs.normalize(help_dir)) + --- XXX: Append tags from netrw, until we remove it... + local netrwtags = get_helptags(vim.fs.normalize('$VIMRUNTIME/pack/dist/opt/netrw/doc/')) + tagmap = vim.tbl_extend('keep', tagmap, netrwtags) helpfiles = get_helpfiles(help_dir, include) parser_path = parser_path and vim.fs.normalize(parser_path) or nil @@ -1424,7 +1428,7 @@ function M.validate(help_dir, include, parser_path) } end ---- Validates vimdoc files on $VIMRUNTIME. and print human-readable error messages if fails. +--- Validates vimdoc files in $VIMRUNTIME, and prints error messages on failure. --- --- If this fails, try these steps (in order): --- 1. Fix/cleanup the :help docs. @@ -1464,7 +1468,7 @@ function M.test_gen(help_dir) print('doc path = ' .. vim.uv.fs_realpath(help_dir)) -- Because gen() is slow (~30s), this test is limited to a few files. - local input = { 'help.txt', 'index.txt', 'nvim.txt' } + local input = { 'api.txt', 'index.txt', 'nvim.txt' } local rv = M.gen(help_dir, tmpdir, input) eq(#input, #rv.helpfiles) eq(0, rv.err_count, 'parse errors in :help docs') diff --git a/src/gen/gen_keycodes.lua b/src/gen/gen_keycodes.lua new file mode 100644 index 0000000000..53f0c24e58 --- /dev/null +++ b/src/gen/gen_keycodes.lua @@ -0,0 +1,86 @@ +local names_file = arg[1] + +local hashy = require('gen.hashy') +local keycodes = require('nvim.keycodes') + +local keycode_names = keycodes.names + +--- @type table<string,integer> +--- Maps lower-case key names to their original indexes. +local name_orig_idx = {} + +--- @type table<string,integer> +--- Maps keys to the original indexes of their preferred names. +local key_orig_idx = {} + +--- @type [string, string][] +--- When multiple keys have the same name (e.g. TAB and K_TAB), only the first one +--- is added to the two tables above, and the other keys are added here. +local extra_keys = {} + +for i, keycode in ipairs(keycode_names) do + local key = keycode[1] + local name = keycode[2] + local name_lower = name:lower() + if name_orig_idx[name_lower] == nil then + name_orig_idx[name_lower] = i + if key_orig_idx[key] == nil then + key_orig_idx[key] = i + end + else + table.insert(extra_keys, keycode) + end +end + +local hashorder = vim.tbl_keys(name_orig_idx) +table.sort(hashorder) +local hashfun +hashorder, hashfun = hashy.hashy_hash('get_special_key_code', hashorder, function(idx) + return 'key_names_table[' .. idx .. '].name.data' +end, true) + +--- @type table<string,integer> +--- Maps keys to the (after hash) indexes of the entries with preferred names. +local key_hash_idx = {} + +for i, lower_name in ipairs(hashorder) do + local orig_idx = name_orig_idx[lower_name] + local key = keycode_names[orig_idx][1] + if key_orig_idx[key] == orig_idx then + key_hash_idx[key] = i + end +end + +local names_tgt = assert(io.open(names_file, 'w')) +names_tgt:write([[ +static const struct key_name_entry { + int key; ///< Special key code or ascii value + String name; ///< Name of key + const String *pref_name; ///< Pointer to preferred key name + ///< (may be NULL or point to the name in another entry) +} key_names_table[] = {]]) + +for i, lower_name in ipairs(hashorder) do + local keycode = keycode_names[name_orig_idx[lower_name]] + local key = keycode[1] + local name = keycode[2] + local pref_idx = key_hash_idx[key] + names_tgt:write( + ('\n {%s, {"%s", %u}, %s},'):format( + key, + name, + #name, + pref_idx == i and 'NULL' or ('&key_names_table[%u].name'):format(pref_idx - 1) + ) + ) +end + +for _, keycode in ipairs(extra_keys) do + local key = keycode[1] + local name = keycode[2] + names_tgt:write(('\n {%s, {"%s", %u}, NULL},'):format(key, name, #name)) +end + +names_tgt:write('\n};\n\n') +names_tgt:write('static ' .. hashfun) +names_tgt:close() diff --git a/src/gen/gen_vimvim.lua b/src/gen/gen_vimvim.lua index d2b1f48a4c..6545483bf3 100644 --- a/src/gen/gen_vimvim.lua +++ b/src/gen/gen_vimvim.lua @@ -113,19 +113,12 @@ w('\nsyn case ignore') local vimau_start = 'syn keyword vimAutoEvent contained ' w('\n\n' .. vimau_start) -for _, au in ipairs(auevents.events) do - if not auevents.nvim_specific[au[1]] then +for au, _ in vim.spairs(vim.tbl_extend('error', auevents.events, auevents.aliases)) do + if not auevents.nvim_specific[au] then if lld.line_length > 850 then w('\n' .. vimau_start) end - w(' ' .. au[1]) - for _, alias in ipairs(au[2]) do - if lld.line_length > 850 then - w('\n' .. vimau_start) - end - -- au[1] is aliased to alias - w(' ' .. alias) - end + w(' ' .. au) end end diff --git a/src/gen/hashy.lua b/src/gen/hashy.lua index 74b7655324..cd0e90830d 100644 --- a/src/gen/hashy.lua +++ b/src/gen/hashy.lua @@ -54,7 +54,7 @@ function M.build_pos_hash(strings) return len_pos_buckets, maxlen, worst_buck_size end -function M.switcher(put, tab, maxlen, worst_buck_size) +function M.switcher(put, tab, maxlen, worst_buck_size, icase) local neworder = {} --- @type string[] put ' switch (len) {\n' local bucky = worst_buck_size > 1 @@ -72,7 +72,11 @@ function M.switcher(put, tab, maxlen, worst_buck_size) local startidx = #neworder vim.list_extend(neworder, buck) local endidx = #neworder - put(" case '" .. c .. "': ") + if icase and c:upper() ~= c:lower() then + put((" case '%s': case '%s': "):format(c:upper(), c:lower())) + else + put((" case '%s': "):format(c)) + end if len == 1 then put('return ' .. startidx .. ';\n') else @@ -102,7 +106,9 @@ function M.switcher(put, tab, maxlen, worst_buck_size) return neworder end -function M.hashy_hash(name, strings, access) +--- @param icase? boolean generate a case-insensitive hash function. +--- `strings` must not have mixed case when using this. +function M.hashy_hash(name, strings, access, icase) local stats = {} local put = function(str) table.insert(stats, str) @@ -116,27 +122,27 @@ function M.hashy_hash(name, strings, access) else put(' int low = -1;\n') end - local neworder = M.switcher(put, len_pos_buckets, maxlen, worst_buck_size) + local neworder = M.switcher(put, len_pos_buckets, maxlen, worst_buck_size, icase) if maxlen == 1 then put([[ return -1; ]]) elseif worst_buck_size > 1 then - put([[ + put(([[ for (int i = low; i < high; i++) { - if (!memcmp(str, ]] .. access('i') .. [[, len)) { + if (!%s(str, %s, len)) { return i; } } return -1; -]]) +]]):format(icase and 'vim_strnicmp_asc' or 'memcmp', access('i'))) else - put([[ - if (low < 0 || memcmp(str, ]] .. access('low') .. [[, len)) { + put(([[ + if (low < 0 || %s(str, %s, len)) { return -1; } return low; -]]) +]]):format(icase and 'vim_strnicmp_asc' or 'memcmp', access('low'))) end put '}\n\n' return neworder, table.concat(stats) diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 36bcd5fbce..355c3c2e6b 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -304,6 +304,7 @@ set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua) set(EVENTS_GENERATOR ${GENERATOR_DIR}/gen_events.lua) set(EX_CMDS_GENERATOR ${GENERATOR_DIR}/gen_ex_cmds.lua) set(FUNCS_GENERATOR ${GENERATOR_DIR}/gen_eval.lua) +set(KEYCODES_GENERATOR ${GENERATOR_DIR}/gen_keycodes.lua) set(GENERATOR_C_GRAMMAR ${GENERATOR_DIR}/c_grammar.lua) set(GENERATOR_HASHY ${GENERATOR_DIR}/hashy.lua) set(GENERATOR_PRELOAD ${GENERATOR_DIR}/preload_nlua.lua) @@ -318,6 +319,7 @@ set(GENERATED_EVENTS_NAMES_MAP ${GENERATED_DIR}/auevents_name_map.generated.h) set(GENERATED_EX_CMDS_DEFS ${GENERATED_DIR}/ex_cmds_defs.generated.h) set(GENERATED_EX_CMDS_ENUM ${GENERATED_INCLUDES_DIR}/ex_cmds_enum.generated.h) set(GENERATED_FUNCS ${GENERATED_DIR}/funcs.generated.h) +set(GENERATED_KEYCODE_NAMES ${GENERATED_DIR}/keycode_names.generated.h) set(GENERATED_API_METADATA ${GENERATED_DIR}/api/private/api_metadata.generated.h) set(GENERATED_KEYSETS_DEFS ${GENERATED_DIR}/keysets_defs.generated.h) set(GENERATED_OPTIONS ${GENERATED_DIR}/options.generated.h) @@ -601,6 +603,7 @@ add_custom_command( ${MSGPACK_RPC_HEADERS} ${API_DISPATCH_GENERATOR} ${GENERATOR_C_GRAMMAR} + ${GENERATOR_HASHY} ${UI_METADATA} ${NVIM_VERSION_LUA} ${NVIM_VERSION_GIT_H} @@ -658,27 +661,10 @@ add_custom_command( ${LUA_GEN_DEPS} ${API_UI_EVENTS_GENERATOR} ${GENERATOR_C_GRAMMAR} + ${GENERATOR_HASHY} ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h ) -list(APPEND NVIM_GENERATED_FOR_HEADERS - "${GENERATED_EX_CMDS_ENUM}" - "${GENERATED_EVENTS_ENUM}" - "${GENERATED_KEYSETS_DEFS}" - "${GENERATED_OPTIONS_ENUM}" - "${GENERATED_OPTION_VARS}" -) - -list(APPEND NVIM_GENERATED_FOR_SOURCES - "${GENERATED_API_DISPATCH}" - "${GENERATED_EX_CMDS_DEFS}" - "${GENERATED_EVENTS_NAMES_MAP}" - "${GENERATED_OPTIONS}" - "${GENERATED_OPTIONS_MAP}" - "${VIM_MODULE_FILE}" - "${PROJECT_BINARY_DIR}/cmake.config/auto/pathdef.h" -) - add_custom_command(OUTPUT ${GENERATED_EX_CMDS_ENUM} ${GENERATED_EX_CMDS_DEFS} COMMAND ${LUA_GEN} ${EX_CMDS_GENERATOR} ${GENERATED_INCLUDES_DIR} ${GENERATED_DIR} DEPENDS ${LUA_GEN_DEPS} ${EX_CMDS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/ex_cmds.lua @@ -686,19 +672,42 @@ add_custom_command(OUTPUT ${GENERATED_EX_CMDS_ENUM} ${GENERATED_EX_CMDS_DEFS} add_custom_command(OUTPUT ${GENERATED_FUNCS} ${FUNCS_DATA} COMMAND ${LUA_GEN} ${FUNCS_GENERATOR} ${GENERATED_DIR} ${FUNCS_METADATA} ${FUNCS_DATA} - DEPENDS ${LUA_GEN_DEPS} ${FUNCS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/eval.lua ${FUNCS_METADATA} + DEPENDS ${LUA_GEN_DEPS} ${FUNCS_GENERATOR} ${GENERATOR_HASHY} ${CMAKE_CURRENT_LIST_DIR}/eval.lua ${FUNCS_METADATA} ) -list(APPEND NVIM_GENERATED_FOR_SOURCES - "${GENERATED_FUNCS}") add_custom_command(OUTPUT ${GENERATED_EVENTS_ENUM} ${GENERATED_EVENTS_NAMES_MAP} COMMAND ${LUA_GEN} ${EVENTS_GENERATOR} ${GENERATED_EVENTS_ENUM} ${GENERATED_EVENTS_NAMES_MAP} DEPENDS ${LUA_GEN_DEPS} ${EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/auevents.lua ) +add_custom_command(OUTPUT ${GENERATED_KEYCODE_NAMES} + COMMAND ${LUA_GEN} ${KEYCODES_GENERATOR} ${GENERATED_KEYCODE_NAMES} + DEPENDS ${LUA_GEN_DEPS} ${KEYCODES_GENERATOR} ${GENERATOR_HASHY} ${CMAKE_CURRENT_LIST_DIR}/keycodes.lua +) + add_custom_command(OUTPUT ${GENERATED_OPTIONS} ${GENERATED_OPTIONS_ENUM} ${GENERATED_OPTIONS_MAP} ${GENERATED_OPTION_VARS} COMMAND ${LUA_GEN} ${OPTIONS_GENERATOR} ${GENERATED_OPTIONS} ${GENERATED_OPTIONS_ENUM} ${GENERATED_OPTIONS_MAP} ${GENERATED_OPTION_VARS} - DEPENDS ${LUA_GEN_DEPS} ${OPTIONS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/options.lua + DEPENDS ${LUA_GEN_DEPS} ${OPTIONS_GENERATOR} ${GENERATOR_HASHY} ${CMAKE_CURRENT_LIST_DIR}/options.lua +) + +list(APPEND NVIM_GENERATED_FOR_HEADERS + "${GENERATED_EX_CMDS_ENUM}" + "${GENERATED_EVENTS_ENUM}" + "${GENERATED_KEYSETS_DEFS}" + "${GENERATED_OPTIONS_ENUM}" + "${GENERATED_OPTION_VARS}" +) + +list(APPEND NVIM_GENERATED_FOR_SOURCES + "${GENERATED_API_DISPATCH}" + "${GENERATED_EX_CMDS_DEFS}" + "${GENERATED_EVENTS_NAMES_MAP}" + "${GENERATED_FUNCS}" + "${GENERATED_KEYCODE_NAMES}" + "${GENERATED_OPTIONS}" + "${GENERATED_OPTIONS_MAP}" + "${VIM_MODULE_FILE}" + "${PROJECT_BINARY_DIR}/cmake.config/auto/pathdef.h" ) # NVIM_GENERATED_FOR_SOURCES and NVIM_GENERATED_FOR_HEADERS must be mutually exclusive. diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 41a09999d0..aa9dc1098e 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -507,7 +507,11 @@ void nvim_ui_term_event(uint64_t channel_id, String event, Object value, Error * const String termresponse = value.data.string; set_vim_var_string(VV_TERMRESPONSE, termresponse.data, (ptrdiff_t)termresponse.size); - apply_autocmds_group(EVENT_TERMRESPONSE, NULL, NULL, false, AUGROUP_ALL, NULL, NULL, &value); + + MAXSIZE_TEMP_DICT(data, 1); + PUT_C(data, "sequence", value); + apply_autocmds_group(EVENT_TERMRESPONSE, NULL, NULL, true, AUGROUP_ALL, NULL, NULL, + &DICT_OBJ(data)); } } diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 9655fef0a4..c5e7186713 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -1,142 +1,153 @@ return { - --- @type [string, string[], boolean][] List of [eventname, aliases, window-local event] tuples. + --- @type table<string,boolean> + --- Keys are events names. + --- Values are boolean indicating whether the event is window-local. events = { - { 'BufAdd', { 'BufCreate' }, true }, -- after adding a buffer to the buffer list - { 'BufDelete', {}, true }, -- deleting a buffer from the buffer list - { 'BufEnter', {}, true }, -- after entering a buffer - { 'BufFilePost', {}, true }, -- after renaming a buffer - { 'BufFilePre', {}, true }, -- before renaming a buffer - { 'BufHidden', {}, true }, -- just after buffer becomes hidden - { 'BufLeave', {}, true }, -- before leaving a buffer - { 'BufModifiedSet', {}, true }, -- after the 'modified' state of a buffer changes - { 'BufNew', {}, true }, -- after creating any buffer - { 'BufNewFile', {}, true }, -- when creating a buffer for a new file - { 'BufReadCmd', {}, true }, -- read buffer using command - { 'BufReadPost', { 'BufRead' }, true }, -- after reading a buffer - { 'BufReadPre', {}, true }, -- before reading a buffer - { 'BufUnload', {}, true }, -- just before unloading a buffer - { 'BufWinEnter', {}, true }, -- after showing a buffer in a window - { 'BufWinLeave', {}, true }, -- just after buffer removed from window - { 'BufWipeout', {}, true }, -- just before really deleting a buffer - { 'BufWriteCmd', {}, true }, -- write buffer using command - { 'BufWritePost', {}, true }, -- after writing a buffer - { 'BufWritePre', { 'BufWrite' }, true }, -- before writing a buffer - { 'ChanInfo', {}, false }, -- info was received about channel - { 'ChanOpen', {}, false }, -- channel was opened - { 'CmdUndefined', {}, false }, -- command undefined - { 'CmdWinEnter', {}, false }, -- after entering the cmdline window - { 'CmdWinLeave', {}, false }, -- before leaving the cmdline window - { 'CmdlineChanged', {}, false }, -- command line was modified - { 'CmdlineEnter', {}, false }, -- after entering cmdline mode - { 'CmdlineLeave', {}, false }, -- before leaving cmdline mode - { 'ColorScheme', {}, false }, -- after loading a colorscheme - { 'ColorSchemePre', {}, false }, -- before loading a colorscheme - { 'CompleteChanged', {}, false }, -- after popup menu changed - { 'CompleteDone', {}, false }, -- after finishing insert complete - { 'CompleteDonePre', {}, false }, -- idem, before clearing info - { 'CursorHold', {}, true }, -- cursor in same position for a while - { 'CursorHoldI', {}, true }, -- idem, in Insert mode - { 'CursorMoved', {}, true }, -- cursor was moved - { 'CursorMovedC', {}, true }, -- cursor was moved in Cmdline mode - { 'CursorMovedI', {}, true }, -- cursor was moved in Insert mode - { 'DiagnosticChanged', {}, false }, -- diagnostics in a buffer were modified - { 'DiffUpdated', {}, false }, -- diffs have been updated - { 'DirChanged', {}, false }, -- directory changed - { 'DirChangedPre', {}, false }, -- directory is going to change - { 'EncodingChanged', { 'FileEncoding' }, false }, -- after changing the 'encoding' option - { 'ExitPre', {}, false }, -- before exiting - { 'FileAppendCmd', {}, true }, -- append to a file using command - { 'FileAppendPost', {}, true }, -- after appending to a file - { 'FileAppendPre', {}, true }, -- before appending to a file - { 'FileChangedRO', {}, true }, -- before first change to read-only file - { 'FileChangedShell', {}, true }, -- after shell command that changed file - { 'FileChangedShellPost', {}, true }, -- after (not) reloading changed file - { 'FileReadCmd', {}, true }, -- read from a file using command - { 'FileReadPost', {}, true }, -- after reading a file - { 'FileReadPre', {}, true }, -- before reading a file - { 'FileType', {}, true }, -- new file type detected (user defined) - { 'FileWriteCmd', {}, true }, -- write to a file using command - { 'FileWritePost', {}, true }, -- after writing a file - { 'FileWritePre', {}, true }, -- before writing a file - { 'FilterReadPost', {}, true }, -- after reading from a filter - { 'FilterReadPre', {}, true }, -- before reading from a filter - { 'FilterWritePost', {}, true }, -- after writing to a filter - { 'FilterWritePre', {}, true }, -- before writing to a filter - { 'FocusGained', {}, false }, -- got the focus - { 'FocusLost', {}, false }, -- lost the focus to another app - { 'FuncUndefined', {}, false }, -- if calling a function which doesn't exist - { 'GUIEnter', {}, false }, -- after starting the GUI - { 'GUIFailed', {}, false }, -- after starting the GUI failed - { 'InsertChange', {}, true }, -- when changing Insert/Replace mode - { 'InsertCharPre', {}, true }, -- before inserting a char - { 'InsertEnter', {}, true }, -- when entering Insert mode - { 'InsertLeave', {}, true }, -- just after leaving Insert mode - { 'InsertLeavePre', {}, true }, -- just before leaving Insert mode - { 'LspAttach', {}, false }, -- after an LSP client attaches to a buffer - { 'LspDetach', {}, false }, -- after an LSP client detaches from a buffer - { 'LspNotify', {}, false }, -- after an LSP notice has been sent to the server - { 'LspProgress', {}, false }, -- after a LSP progress update - { 'LspRequest', {}, false }, -- after an LSP request is started, canceled, or completed - { 'LspTokenUpdate', {}, false }, -- after a visible LSP token is updated - { 'MenuPopup', {}, false }, -- just before popup menu is displayed - { 'ModeChanged', {}, false }, -- after changing the mode - { 'OptionSet', {}, false }, -- after setting any option - { 'QuickFixCmdPost', {}, false }, -- after :make, :grep etc. - { 'QuickFixCmdPre', {}, false }, -- before :make, :grep etc. - { 'QuitPre', {}, false }, -- before :quit - { 'RecordingEnter', {}, true }, -- when starting to record a macro - { 'RecordingLeave', {}, true }, -- just before a macro stops recording - { 'RemoteReply', {}, false }, -- upon string reception from a remote vim - { 'SafeState', {}, false }, -- going to wait for a character - { 'SearchWrapped', {}, true }, -- after the search wrapped around - { 'SessionLoadPost', {}, false }, -- after loading a session file - { 'SessionWritePost', {}, false }, -- after writing a session file - { 'ShellCmdPost', {}, false }, -- after ":!cmd" - { 'ShellFilterPost', {}, true }, -- after ":1,2!cmd", ":w !cmd", ":r !cmd". - { 'Signal', {}, false }, -- after nvim process received a signal - { 'SourceCmd', {}, false }, -- sourcing a Vim script using command - { 'SourcePost', {}, false }, -- after sourcing a Vim script - { 'SourcePre', {}, false }, -- before sourcing a Vim script - { 'SpellFileMissing', {}, false }, -- spell file missing - { 'StdinReadPost', {}, false }, -- after reading from stdin - { 'StdinReadPre', {}, false }, -- before reading from stdin - { 'SwapExists', {}, false }, -- found existing swap file - { 'Syntax', {}, false }, -- syntax selected - { 'TabClosed', {}, false }, -- a tab has closed - { 'TabEnter', {}, false }, -- after entering a tab page - { 'TabLeave', {}, false }, -- before leaving a tab page - { 'TabNew', {}, false }, -- when creating a new tab - { 'TabNewEntered', {}, false }, -- after entering a new tab - { 'TermChanged', {}, false }, -- after changing 'term' - { 'TermClose', {}, false }, -- after the process exits - { 'TermEnter', {}, false }, -- after entering Terminal mode - { 'TermLeave', {}, false }, -- after leaving Terminal mode - { 'TermOpen', {}, false }, -- after opening a terminal buffer - { 'TermRequest', {}, false }, -- after an unhandled OSC sequence is emitted - { 'TermResponse', {}, false }, -- after setting "v:termresponse" - { 'TextChanged', {}, true }, -- text was modified - { 'TextChangedI', {}, true }, -- text was modified in Insert mode(no popup) - { 'TextChangedP', {}, true }, -- text was modified in Insert mode(popup) - { 'TextChangedT', {}, true }, -- text was modified in Terminal mode - { 'TextYankPost', {}, true }, -- after a yank or delete was done (y, d, c) - { 'UIEnter', {}, false }, -- after UI attaches - { 'UILeave', {}, false }, -- after UI detaches - { 'User', {}, false }, -- user defined autocommand - { 'VimEnter', {}, false }, -- after starting Vim - { 'VimLeave', {}, false }, -- before exiting Vim - { 'VimLeavePre', {}, false }, -- before exiting Vim and writing ShaDa file - { 'VimResized', {}, false }, -- after Vim window was resized - { 'VimResume', {}, false }, -- after Nvim is resumed - { 'VimSuspend', {}, false }, -- before Nvim is suspended - { 'WinClosed', {}, true }, -- after closing a window - { 'WinEnter', {}, true }, -- after entering a window - { 'WinLeave', {}, true }, -- before leaving a window - { 'WinNew', {}, false }, -- when entering a new window - { 'WinResized', {}, true }, -- after a window was resized - { 'WinScrolled', {}, true }, -- after a window was scrolled or resized + BufAdd = true, -- after adding a buffer to the buffer list + BufDelete = true, -- deleting a buffer from the buffer list + BufEnter = true, -- after entering a buffer + BufFilePost = true, -- after renaming a buffer + BufFilePre = true, -- before renaming a buffer + BufHidden = true, -- just after buffer becomes hidden + BufLeave = true, -- before leaving a buffer + BufModifiedSet = true, -- after the 'modified' state of a buffer changes + BufNew = true, -- after creating any buffer + BufNewFile = true, -- when creating a buffer for a new file + BufReadCmd = true, -- read buffer using command + BufReadPost = true, -- after reading a buffer + BufReadPre = true, -- before reading a buffer + BufUnload = true, -- just before unloading a buffer + BufWinEnter = true, -- after showing a buffer in a window + BufWinLeave = true, -- just after buffer removed from window + BufWipeout = true, -- just before really deleting a buffer + BufWriteCmd = true, -- write buffer using command + BufWritePost = true, -- after writing a buffer + BufWritePre = true, -- before writing a buffer + ChanInfo = false, -- info was received about channel + ChanOpen = false, -- channel was opened + CmdUndefined = false, -- command undefined + CmdWinEnter = false, -- after entering the cmdline window + CmdWinLeave = false, -- before leaving the cmdline window + CmdlineChanged = false, -- command line was modified + CmdlineEnter = false, -- after entering cmdline mode + CmdlineLeave = false, -- before leaving cmdline mode + ColorScheme = false, -- after loading a colorscheme + ColorSchemePre = false, -- before loading a colorscheme + CompleteChanged = false, -- after popup menu changed + CompleteDone = false, -- after finishing insert complete + CompleteDonePre = false, -- idem, before clearing info + CursorHold = true, -- cursor in same position for a while + CursorHoldI = true, -- idem, in Insert mode + CursorMoved = true, -- cursor was moved + CursorMovedC = true, -- cursor was moved in Cmdline mode + CursorMovedI = true, -- cursor was moved in Insert mode + DiagnosticChanged = false, -- diagnostics in a buffer were modified + DiffUpdated = false, -- diffs have been updated + DirChanged = false, -- directory changed + DirChangedPre = false, -- directory is going to change + EncodingChanged = false, -- after changing the 'encoding' option + ExitPre = false, -- before exiting + FileAppendCmd = true, -- append to a file using command + FileAppendPost = true, -- after appending to a file + FileAppendPre = true, -- before appending to a file + FileChangedRO = true, -- before first change to read-only file + FileChangedShell = true, -- after shell command that changed file + FileChangedShellPost = true, -- after (not) reloading changed file + FileReadCmd = true, -- read from a file using command + FileReadPost = true, -- after reading a file + FileReadPre = true, -- before reading a file + FileType = true, -- new file type detected (user defined) + FileWriteCmd = true, -- write to a file using command + FileWritePost = true, -- after writing a file + FileWritePre = true, -- before writing a file + FilterReadPost = true, -- after reading from a filter + FilterReadPre = true, -- before reading from a filter + FilterWritePost = true, -- after writing to a filter + FilterWritePre = true, -- before writing to a filter + FocusGained = false, -- got the focus + FocusLost = false, -- lost the focus to another app + FuncUndefined = false, -- if calling a function which doesn't exist + GUIEnter = false, -- after starting the GUI + GUIFailed = false, -- after starting the GUI failed + InsertChange = true, -- when changing Insert/Replace mode + InsertCharPre = true, -- before inserting a char + InsertEnter = true, -- when entering Insert mode + InsertLeave = true, -- just after leaving Insert mode + InsertLeavePre = true, -- just before leaving Insert mode + LspAttach = false, -- after an LSP client attaches to a buffer + LspDetach = false, -- after an LSP client detaches from a buffer + LspNotify = false, -- after an LSP notice has been sent to the server + LspProgress = false, -- after a LSP progress update + LspRequest = false, -- after an LSP request is started, canceled, or completed + LspTokenUpdate = false, -- after a visible LSP token is updated + MenuPopup = false, -- just before popup menu is displayed + ModeChanged = false, -- after changing the mode + OptionSet = false, -- after setting any option + QuickFixCmdPost = false, -- after :make, :grep etc. + QuickFixCmdPre = false, -- before :make, :grep etc. + QuitPre = false, -- before :quit + RecordingEnter = true, -- when starting to record a macro + RecordingLeave = true, -- just before a macro stops recording + RemoteReply = false, -- upon string reception from a remote vim + SafeState = false, -- going to wait for a character + SearchWrapped = true, -- after the search wrapped around + SessionLoadPost = false, -- after loading a session file + SessionWritePost = false, -- after writing a session file + ShellCmdPost = false, -- after ":!cmd" + ShellFilterPost = true, -- after ":1,2!cmd", ":w !cmd", ":r !cmd". + Signal = false, -- after nvim process received a signal + SourceCmd = false, -- sourcing a Vim script using command + SourcePost = false, -- after sourcing a Vim script + SourcePre = false, -- before sourcing a Vim script + SpellFileMissing = false, -- spell file missing + StdinReadPost = false, -- after reading from stdin + StdinReadPre = false, -- before reading from stdin + SwapExists = false, -- found existing swap file + Syntax = false, -- syntax selected + TabClosed = false, -- a tab has closed + TabEnter = false, -- after entering a tab page + TabLeave = false, -- before leaving a tab page + TabNew = false, -- when creating a new tab + TabNewEntered = false, -- after entering a new tab + TermChanged = false, -- after changing 'term' + TermClose = false, -- after the process exits + TermEnter = false, -- after entering Terminal mode + TermLeave = false, -- after leaving Terminal mode + TermOpen = false, -- after opening a terminal buffer + TermRequest = false, -- after an unhandled OSC sequence is emitted + TermResponse = false, -- after setting "v:termresponse" + TextChanged = true, -- text was modified + TextChangedI = true, -- text was modified in Insert mode(no popup) + TextChangedP = true, -- text was modified in Insert mode(popup) + TextChangedT = true, -- text was modified in Terminal mode + TextYankPost = true, -- after a yank or delete was done (y, d, c) + UIEnter = false, -- after UI attaches + UILeave = false, -- after UI detaches + User = false, -- user defined autocommand + VimEnter = false, -- after starting Vim + VimLeave = false, -- before exiting Vim + VimLeavePre = false, -- before exiting Vim and writing ShaDa file + VimResized = false, -- after Vim window was resized + VimResume = false, -- after Nvim is resumed + VimSuspend = false, -- before Nvim is suspended + WinClosed = true, -- after closing a window + WinEnter = true, -- after entering a window + WinLeave = true, -- before leaving a window + WinNew = false, -- when entering a new window + WinResized = true, -- after a window was resized + WinScrolled = true, -- after a window was scrolled or resized }, - -- List of nvim-specific events or aliases for the purpose of generating - -- syntax file + --- @type table<string,string> + --- Keys are event aliases. + --- Values are the names in the `events` table above. + aliases = { + BufCreate = 'BufAdd', + BufRead = 'BufReadPost', + BufWrite = 'BufWritePre', + FileEncoding = 'EncodingChanged', + }, + --- @type table<string,true> + --- List of Nvim-specific events or aliases for generating syntax file. nvim_specific = { BufModifiedSet = true, DiagnosticChanged = true, diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 34ad734528..51d952ed3e 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -658,12 +658,7 @@ event_T event_name2nr_str(String str) const char *event_nr2name(event_T event) FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST { - for (int i = 0; event_names[i].name != NULL; i++) { - if ((event_T)abs(event_names[i].event) == event) { - return event_names[i].name; - } - } - return "Unknown"; + return event >= 0 && event < NUM_EVENTS ? event_names[event].name : "Unknown"; } /// Return true if "event" is included in 'eventignore(win)'. diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 95cee3c442..87d548bfab 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -810,8 +810,8 @@ struct tabpage_S { typedef struct { linenr_T wl_lnum; // buffer line number for logical line uint16_t wl_size; // height in screen lines - char wl_valid; // true values are valid for text in buffer - char wl_folded; // true when this is a range of folded lines + bool wl_valid; // true values are valid for text in buffer + bool wl_folded; // true when this is a range of folded lines linenr_T wl_foldend; // last buffer line number for folded line linenr_T wl_lastlnum; // last buffer line number for logical line } wline_T; diff --git a/src/nvim/change.c b/src/nvim/change.c index 84053619d7..28854ae8aa 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -208,8 +208,9 @@ static void changed_lines_invalidate_win(win_T *wp, linenr_T lnum, colnr_T col, wp->w_lines[i].wl_foldend += xtra; wp->w_lines[i].wl_lastlnum += xtra; } - } else if (wp->w_lines[i].wl_foldend >= lnum) { - // change somewhere inside this range of folded lines, + } else if (wp->w_lines[i].wl_foldend >= lnum + || wp->w_lines[i].wl_lastlnum >= lnum) { + // change somewhere inside this range of folded or concealed lines, // may need to be redrawn wp->w_lines[i].wl_valid = false; } @@ -1315,7 +1316,8 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // May do indenting after opening a new line. bool do_cindent = !p_paste && (curbuf->b_p_cin || *curbuf->b_p_inde != NUL) && in_cinkeys(dir == FORWARD ? KEY_OPEN_FORW : KEY_OPEN_BACK, - ' ', linewhite(curwin->w_cursor.lnum)); + ' ', linewhite(curwin->w_cursor.lnum)) + && !(flags & OPENLINE_FORCE_INDENT); // Find out if the current line starts with a comment leader. // This may then be inserted in front of the new line. diff --git a/src/nvim/channel.c b/src/nvim/channel.c index c964748a20..9fc0a82ca3 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -897,7 +897,7 @@ static void set_info_event(void **argv) tv_dict_add_dict(dict, S_LEN("info"), retval.vval.v_dict); tv_dict_set_keys_readonly(dict); - apply_autocmds(event, NULL, NULL, false, curbuf); + apply_autocmds(event, NULL, NULL, true, curbuf); restore_v_event(dict, &save_v_event); arena_mem_free(arena_finish(&arena)); diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 33ccc834d2..fcfeda5482 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -288,7 +288,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape) p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); } // Translate string into pattern and expand it. - const int use_options = (options + const int use_options = ((options & ~WILD_KEEP_SOLE_ITEM) | WILD_HOME_REPLACE | WILD_ADD_SLASH | WILD_SILENT @@ -339,7 +339,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape) if (xp->xp_numfiles <= 0 && p2 == NULL) { beep_flush(); - } else if (xp->xp_numfiles == 1) { + } else if (xp->xp_numfiles == 1 && !(options & WILD_KEEP_SOLE_ITEM)) { // free expanded pattern ExpandOne(xp, NULL, NULL, 0, WILD_FREE); } diff --git a/src/nvim/cmdexpand.h b/src/nvim/cmdexpand.h index 33ff787589..59c734e9a3 100644 --- a/src/nvim/cmdexpand.h +++ b/src/nvim/cmdexpand.h @@ -40,6 +40,7 @@ enum { WILD_NOERROR = 0x800, ///< sets EW_NOERROR WILD_BUFLASTUSED = 0x1000, BUF_DIFF_FILTER = 0x2000, + WILD_KEEP_SOLE_ITEM = 0x4000, }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index a645dced40..e958610f20 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -2118,13 +2118,13 @@ static void win_update(win_T *wp) if (wp == curwin && lnum == curwin->w_cursor.lnum) { conceal_cursor_used = conceal_cursor_line(curwin); } - if (idx > 0) { - wp->w_lines[idx - 1].wl_lastlnum = lnum + foldinfo.fi_lines - (foldinfo.fi_lines != 0); - } - if (lnum == mod_top && lnum < mod_bot) { - mod_top += foldinfo.fi_lines ? foldinfo.fi_lines : 1; - } if (win_get_fill(wp, lnum) == 0) { + if (idx > 0) { + wp->w_lines[idx - 1].wl_lastlnum = lnum + foldinfo.fi_lines - (foldinfo.fi_lines != 0); + } + if (lnum == mod_top && lnum < mod_bot) { + mod_top += foldinfo.fi_lines ? foldinfo.fi_lines : 1; + } lnum += foldinfo.fi_lines ? foldinfo.fi_lines : 1; spv.spv_capcol_lnum = 0; continue; @@ -2319,6 +2319,13 @@ static void win_update(win_T *wp) wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines; did_update = DID_FOLD; } + + // Adjust "wl_lastlnum" for concealed lines below the last line in the window. + while (row == wp->w_grid.rows + && decor_conceal_line(wp, wp->w_lines[idx].wl_lastlnum, false)) { + wp->w_lines[idx].wl_lastlnum++; + hasFolding(wp, wp->w_lines[idx].wl_lastlnum, NULL, &wp->w_lines[idx].wl_lastlnum); + } } wp->w_lines[idx].wl_lnum = lnum; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index f5ba9c1c05..c3f204045a 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -9,6 +9,7 @@ #include <uv.h> #include "klib/kvec.h" +#include "nvim/api/private/defs.h" #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/autocmd_defs.h" @@ -99,7 +100,6 @@ typedef struct { int did_restart_edit; // remember if insert mode was restarted // after a ctrl+o bool nomove; - char *ptr; } InsertState; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -163,15 +163,8 @@ static void insert_enter(InsertState *s) if (s->cmdchar != 'r' && s->cmdchar != 'v') { pos_T save_cursor = curwin->w_cursor; - if (s->cmdchar == 'R') { - s->ptr = "r"; - } else if (s->cmdchar == 'V') { - s->ptr = "v"; - } else { - s->ptr = "i"; - } - - set_vim_var_string(VV_INSERTMODE, s->ptr, 1); + const char *const ptr = s->cmdchar == 'R' ? "r" : s->cmdchar == 'V' ? "v" : "i"; + set_vim_var_string(VV_INSERTMODE, ptr, 1); set_vim_var_string(VV_CHAR, NULL, -1); ins_apply_autocmds(EVENT_INSERTENTER); @@ -288,14 +281,15 @@ static void insert_enter(InsertState *s) // column. Eg after "^O$" or "^O80|". validate_virtcol(curwin); update_curswant(); + const char *ptr; if (((ins_at_eol && curwin->w_cursor.lnum == o_lnum) || curwin->w_curswant > curwin->w_virtcol) - && *(s->ptr = get_cursor_line_ptr() + curwin->w_cursor.col) != NUL) { - if (s->ptr[1] == NUL) { + && *(ptr = get_cursor_line_ptr() + curwin->w_cursor.col) != NUL) { + if (ptr[1] == NUL) { curwin->w_cursor.col++; } else { - s->i = utfc_ptr2len(s->ptr); - if (s->ptr[s->i] == NUL) { + s->i = utfc_ptr2len(ptr); + if (ptr[s->i] == NUL) { curwin->w_cursor.col += s->i; } } @@ -335,12 +329,10 @@ static void insert_enter(InsertState *s) // Get the current length of the redo buffer, those characters have to be // skipped if we want to get to the inserted characters. - s->ptr = get_inserted(); - if (s->ptr == NULL) { - new_insert_skip = 0; - } else { - new_insert_skip = (int)get_inserted_len(); - xfree(s->ptr); + String inserted = get_inserted(); + new_insert_skip = (int)inserted.size; + if (inserted.data != NULL) { + xfree(inserted.data); } old_indent = 0; @@ -2337,14 +2329,14 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) // Save the inserted text for later redo with ^@ and CTRL-A. // Don't do it when "restart_edit" was set and nothing was inserted, // otherwise CTRL-O w and then <Left> will clear "last_insert". - char *ptr = get_inserted(); - int added = ptr == NULL ? 0 : (int)get_inserted_len() - new_insert_skip; + String inserted = get_inserted(); + int added = inserted.data == NULL ? 0 : (int)inserted.size - new_insert_skip; if (did_restart_edit == 0 || added > 0) { xfree(last_insert); - last_insert = ptr; + last_insert = inserted.data; last_insert_skip = added < 0 ? 0 : new_insert_skip; } else { - xfree(ptr); + xfree(inserted.data); } if (!arrow_used && end_insert_pos != NULL) { diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 7cec2aaa06..c8ad61f885 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -4191,7 +4191,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) list_T *args = argvars[0].vval.v_list; Channel **jobs = xcalloc((size_t)tv_list_len(args), sizeof(*jobs)); - MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop); + MultiQueue *waiting_jobs = multiqueue_new(loop_on_put, &main_loop); // Validate, prepare jobs for waiting. int i = 0; diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index f386dd28b9..c5357d507c 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -8,6 +8,7 @@ #include <stdlib.h> #include <string.h> +#include "nvim/api/private/defs.h" #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/autocmd_defs.h" @@ -265,29 +266,16 @@ static void register_closure(ufunc_T *fp) } static char lambda_name[8 + NUMBUFLEN]; -static size_t lambda_namelen = 0; /// @return a name for a lambda. Returned in static memory. -char *get_lambda_name(void) +static String get_lambda_name(void) { static int lambda_no = 0; int n = snprintf(lambda_name, sizeof(lambda_name), "<lambda>%d", ++lambda_no); - if (n < 1) { - lambda_namelen = 0; - } else if (n >= (int)sizeof(lambda_name)) { - lambda_namelen = sizeof(lambda_name) - 1; - } else { - lambda_namelen = (size_t)n; - } - return lambda_name; -} - -/// Get the length of the last lambda name. -size_t get_lambda_name_len(void) -{ - return lambda_namelen; + return cbuf_as_string(lambda_name, + n < 1 ? 0 : (size_t)MIN(n, (int)sizeof(lambda_name) - 1)); } /// Allocate a "ufunc_T" for a function called "name". @@ -371,10 +359,8 @@ int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) int flags = 0; garray_T newlines; - char *name = get_lambda_name(); - size_t namelen = get_lambda_name_len(); - - fp = alloc_ufunc(name, namelen); + String name = get_lambda_name(); + fp = alloc_ufunc(name.data, name.size); pt = xcalloc(1, sizeof(partial_T)); ga_init(&newlines, (int)sizeof(char *), 1); @@ -4142,9 +4128,8 @@ bool set_ref_in_func(char *name, ufunc_T *fp_in, int copyID) /// Registers a luaref as a lambda. char *register_luafunc(LuaRef ref) { - char *name = get_lambda_name(); - size_t namelen = get_lambda_name_len(); - ufunc_T *fp = alloc_ufunc(name, namelen); + String name = get_lambda_name(); + ufunc_T *fp = alloc_ufunc(name.data, name.size); fp->uf_refcount = 1; fp->uf_varargs = true; diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index 15d993cc62..050419b8e9 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -21,9 +21,9 @@ void loop_init(Loop *loop, void *data) loop->closing = false; loop->uv.data = loop; kv_init(loop->children); - loop->events = multiqueue_new_parent(loop_on_put, loop); + loop->events = multiqueue_new(loop_on_put, loop); loop->fast_events = multiqueue_new_child(loop->events); - loop->thread_events = multiqueue_new_parent(NULL, NULL); + loop->thread_events = multiqueue_new(NULL, NULL); uv_mutex_init(&loop->mutex); uv_async_init(&loop->uv, &loop->async, async_cb); uv_signal_init(&loop->uv, &loop->children_watcher); @@ -212,10 +212,7 @@ static void async_cb(uv_async_t *handle) Loop *l = handle->loop->data; uv_mutex_lock(&l->mutex); // Flush thread_events to fast_events for processing on main loop. - while (!multiqueue_empty(l->thread_events)) { - Event ev = multiqueue_get(l->thread_events); - multiqueue_put_event(l->fast_events, ev); - } + multiqueue_move_events(l->fast_events, l->thread_events); uv_mutex_unlock(&l->mutex); } diff --git a/src/nvim/event/multiqueue.c b/src/nvim/event/multiqueue.c index 8646173776..859051e594 100644 --- a/src/nvim/event/multiqueue.c +++ b/src/nvim/event/multiqueue.c @@ -67,7 +67,7 @@ struct multiqueue_item { struct multiqueue { MultiQueue *parent; QUEUE headtail; // circularly-linked - PutCallback put_cb; + PutCallback on_put; // Called on the parent (if any) when an item is enqueued in a child. void *data; size_t size; }; @@ -84,26 +84,28 @@ typedef struct { static Event NILEVENT = { .handler = NULL, .argv = { NULL } }; -MultiQueue *multiqueue_new_parent(PutCallback put_cb, void *data) +/// Creates a new root (parentless) queue, which may gain child queues via `multiqueue_new_child`. +MultiQueue *multiqueue_new(PutCallback on_put, void *data) { - return multiqueue_new(NULL, put_cb, data); + return _multiqueue_new(NULL, on_put, data); } +/// Creates a new queue as a child of a `parent` queue. MultiQueue *multiqueue_new_child(MultiQueue *parent) FUNC_ATTR_NONNULL_ALL { assert(!parent->parent); // parent cannot have a parent, more like a "root" parent->size++; - return multiqueue_new(parent, NULL, NULL); + return _multiqueue_new(parent, NULL, NULL); } -static MultiQueue *multiqueue_new(MultiQueue *parent, PutCallback put_cb, void *data) +static MultiQueue *_multiqueue_new(MultiQueue *parent, PutCallback on_put, void *data) { MultiQueue *rv = xmalloc(sizeof(MultiQueue)); QUEUE_INIT(&rv->headtail); rv->size = 0; rv->parent = parent; - rv->put_cb = put_cb; + rv->on_put = on_put; rv->data = data; return rv; } @@ -135,8 +137,18 @@ void multiqueue_put_event(MultiQueue *self, Event event) { assert(self); multiqueue_push(self, event); - if (self->parent && self->parent->put_cb) { - self->parent->put_cb(self->parent, self->parent->data); + if (self->parent && self->parent->on_put) { + self->parent->on_put(self->parent, self->parent->data); + } +} + +/// Move events from src to dest. +void multiqueue_move_events(MultiQueue *dest, MultiQueue *src) + FUNC_ATTR_NONNULL_ALL +{ + while (!multiqueue_empty(src)) { + Event event = multiqueue_get(src); + multiqueue_put_event(dest, event); } } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index fc20748309..240bdae6cb 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1082,6 +1082,9 @@ static int command_line_wildchar_complete(CommandLineState *s) if (wim_flags[s->wim_index] & kOptWimFlagLastused) { options |= WILD_BUFLASTUSED; } + if (wim_flags[0] & kOptWimFlagNoselect) { + options |= WILD_KEEP_SOLE_ITEM; + } if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice // if 'wildmode' contains "list" may still need to list if (s->xpc.xp_numfiles > 1 @@ -1124,19 +1127,20 @@ static int command_line_wildchar_complete(CommandLineState *s) // when more than one match, and 'wildmode' first contains // "list", or no change and 'wildmode' contains "longest,list", // list all matches - if (res == OK && s->xpc.xp_numfiles > 1) { + if (res == OK + && s->xpc.xp_numfiles > ((wim_flags[s->wim_index] & kOptWimFlagNoselect) ? 0 : 1)) { // a "longest" that didn't do anything is skipped (but not // "list:longest") if (wim_flags[0] == kOptWimFlagLongest && ccline.cmdpos == j) { s->wim_index = 1; } if ((wim_flags[s->wim_index] & kOptWimFlagList) - || (p_wmnu && (wim_flags[s->wim_index] & kOptWimFlagFull) != 0)) { + || (p_wmnu && (wim_flags[s->wim_index] & (kOptWimFlagFull|kOptWimFlagNoselect)))) { if (!(wim_flags[0] & kOptWimFlagLongest)) { int p_wmnu_save = p_wmnu; p_wmnu = 0; // remove match - nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); + nextwild(&s->xpc, WILD_PREV, options, s->firstc != '@'); p_wmnu = p_wmnu_save; } @@ -1146,7 +1150,8 @@ static int command_line_wildchar_complete(CommandLineState *s) if (wim_flags[s->wim_index] & kOptWimFlagLongest) { nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); - } else if (wim_flags[s->wim_index] & kOptWimFlagFull) { + } else if ((wim_flags[s->wim_index] & kOptWimFlagFull) + && !(wim_flags[s->wim_index] & kOptWimFlagNoselect)) { nextwild(&s->xpc, WILD_NEXT, options, s->firstc != '@'); } } else { @@ -2875,6 +2880,8 @@ int check_opt_wim(void) new_wim_flags[idx] |= kOptWimFlagList; } else if (i == 8 && strncmp(p, "lastused", 8) == 0) { new_wim_flags[idx] |= kOptWimFlagLastused; + } else if (i == 8 && strncmp(p, "noselect", 8) == 0) { + new_wim_flags[idx] |= kOptWimFlagNoselect; } else { return FAIL; } diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index ac817ab9e6..899293e9f6 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -155,9 +155,6 @@ static uint8_t noremapbuf_init[TYPELEN_INIT]; ///< initial typebuf.tb_noremap static size_t last_recorded_len = 0; ///< number of last recorded chars -static size_t last_get_inserted_len = 0; ///< length of the string returned from the - ///< last call to get_inserted() - enum { KEYLEN_PART_KEY = -1, ///< keylen value for incomplete key-code KEYLEN_PART_MAP = -2, ///< keylen value for incomplete mapping @@ -254,15 +251,11 @@ char *get_recorded(void) /// Return the contents of the redo buffer as a single string. /// K_SPECIAL in the returned string is escaped. -char *get_inserted(void) -{ - return get_buffcont(&redobuff, false, &last_get_inserted_len); -} - -/// Return the length of string returned from the last call of get_inserted(). -size_t get_inserted_len(void) +String get_inserted(void) { - return last_get_inserted_len; + size_t len = 0; + char *str = get_buffcont(&redobuff, false, &len); + return cbuf_as_string(str, len); } /// Add string after the current block of the given buffer diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index 766c5eb2a4..aa5110655c 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -3,6 +3,7 @@ #include <stddef.h> // IWYU pragma: keep #include <stdint.h> // IWYU pragma: keep +#include "nvim/api/private/defs.h" // IWYU pragma: keep #include "nvim/eval/typval_defs.h" // IWYU pragma: keep #include "nvim/getchar_defs.h" // IWYU pragma: keep #include "nvim/types_defs.h" // IWYU pragma: keep diff --git a/src/nvim/globals.h b/src/nvim/globals.h index e0bbba3d68..15152bc1a4 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -464,6 +464,8 @@ EXTERN bool VIsual_active INIT( = false); EXTERN bool VIsual_select INIT( = false); /// Register name for Select mode EXTERN int VIsual_select_reg INIT( = 0); +/// Whether incremented cursor during exclusive selection +EXTERN bool VIsual_select_exclu_adj INIT( = false); /// Restart Select mode when next cmd finished EXTERN int restart_VIsual_select INIT( = 0); /// Whether to restart the selection after a Select-mode mapping or menu. diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c index b6ba73f7c1..9cb2321eee 100644 --- a/src/nvim/keycodes.c +++ b/src/nvim/keycodes.c @@ -6,6 +6,7 @@ #include <string.h> #include <uv.h> +#include "nvim/api/private/defs.h" #include "nvim/ascii_defs.h" #include "nvim/charset.h" #include "nvim/errors.h" @@ -24,6 +25,7 @@ #include "nvim/strings.h" #ifdef INCLUDE_GENERATED_DECLARATIONS +# include "keycode_names.generated.h" # include "keycodes.c.generated.h" #endif @@ -142,214 +144,6 @@ static uint8_t modifier_keys_table[] = { NUL }; -static const struct key_name_entry { - int key; // Special key code or ascii value - const char *name; // Name of key -} key_names_table[] = { - { ' ', "Space" }, - { TAB, "Tab" }, - { K_TAB, "Tab" }, - { NL, "NL" }, - { NL, "NewLine" }, // Alternative name - { NL, "LineFeed" }, // Alternative name - { NL, "LF" }, // Alternative name - { CAR, "CR" }, - { CAR, "Return" }, // Alternative name - { CAR, "Enter" }, // Alternative name - { K_BS, "BS" }, - { K_BS, "BackSpace" }, // Alternative name - { ESC, "Esc" }, - { ESC, "Escape" }, // Alternative name - { CSI, "CSI" }, - { '|', "Bar" }, - { '\\', "Bslash" }, - { K_DEL, "Del" }, - { K_DEL, "Delete" }, // Alternative name - { K_KDEL, "kDel" }, - { K_KDEL, "KPPeriod" }, // libtermkey name - { K_UP, "Up" }, - { K_DOWN, "Down" }, - { K_LEFT, "Left" }, - { K_RIGHT, "Right" }, - { K_XUP, "xUp" }, - { K_XDOWN, "xDown" }, - { K_XLEFT, "xLeft" }, - { K_XRIGHT, "xRight" }, - { K_KUP, "kUp" }, - { K_KUP, "KP8" }, - { K_KDOWN, "kDown" }, - { K_KDOWN, "KP2" }, - { K_KLEFT, "kLeft" }, - { K_KLEFT, "KP4" }, - { K_KRIGHT, "kRight" }, - { K_KRIGHT, "KP6" }, - - { K_F1, "F1" }, - { K_F2, "F2" }, - { K_F3, "F3" }, - { K_F4, "F4" }, - { K_F5, "F5" }, - { K_F6, "F6" }, - { K_F7, "F7" }, - { K_F8, "F8" }, - { K_F9, "F9" }, - { K_F10, "F10" }, - - { K_F11, "F11" }, - { K_F12, "F12" }, - { K_F13, "F13" }, - { K_F14, "F14" }, - { K_F15, "F15" }, - { K_F16, "F16" }, - { K_F17, "F17" }, - { K_F18, "F18" }, - { K_F19, "F19" }, - { K_F20, "F20" }, - - { K_F21, "F21" }, - { K_F22, "F22" }, - { K_F23, "F23" }, - { K_F24, "F24" }, - { K_F25, "F25" }, - { K_F26, "F26" }, - { K_F27, "F27" }, - { K_F28, "F28" }, - { K_F29, "F29" }, - { K_F30, "F30" }, - - { K_F31, "F31" }, - { K_F32, "F32" }, - { K_F33, "F33" }, - { K_F34, "F34" }, - { K_F35, "F35" }, - { K_F36, "F36" }, - { K_F37, "F37" }, - { K_F38, "F38" }, - { K_F39, "F39" }, - { K_F40, "F40" }, - - { K_F41, "F41" }, - { K_F42, "F42" }, - { K_F43, "F43" }, - { K_F44, "F44" }, - { K_F45, "F45" }, - { K_F46, "F46" }, - { K_F47, "F47" }, - { K_F48, "F48" }, - { K_F49, "F49" }, - { K_F50, "F50" }, - - { K_F51, "F51" }, - { K_F52, "F52" }, - { K_F53, "F53" }, - { K_F54, "F54" }, - { K_F55, "F55" }, - { K_F56, "F56" }, - { K_F57, "F57" }, - { K_F58, "F58" }, - { K_F59, "F59" }, - { K_F60, "F60" }, - - { K_F61, "F61" }, - { K_F62, "F62" }, - { K_F63, "F63" }, - - { K_XF1, "xF1" }, - { K_XF2, "xF2" }, - { K_XF3, "xF3" }, - { K_XF4, "xF4" }, - - { 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" }, - { K_KINS, "KP0" }, - { K_HOME, "Home" }, - { K_KHOME, "kHome" }, - { K_KHOME, "KP7" }, - { K_XHOME, "xHome" }, - { K_ZHOME, "zHome" }, - { K_END, "End" }, - { K_KEND, "kEnd" }, - { K_KEND, "KP1" }, - { K_XEND, "xEnd" }, - { K_ZEND, "zEnd" }, - { K_PAGEUP, "PageUp" }, - { K_PAGEDOWN, "PageDown" }, - { K_KPAGEUP, "kPageUp" }, - { K_KPAGEUP, "KP9" }, - { K_KPAGEDOWN, "kPageDown" }, - { K_KPAGEDOWN, "KP3" }, - { K_KORIGIN, "kOrigin" }, - { K_KORIGIN, "KP5" }, - - { K_KPLUS, "kPlus" }, - { K_KPLUS, "KPPlus" }, - { K_KMINUS, "kMinus" }, - { K_KMINUS, "KPMinus" }, - { K_KDIVIDE, "kDivide" }, - { K_KDIVIDE, "KPDiv" }, - { K_KMULTIPLY, "kMultiply" }, - { K_KMULTIPLY, "KPMult" }, - { K_KENTER, "kEnter" }, - { K_KENTER, "KPEnter" }, - { K_KPOINT, "kPoint" }, - { K_KCOMMA, "kComma" }, - { K_KCOMMA, "KPComma" }, - { K_KEQUAL, "kEqual" }, - { K_KEQUAL, "KPEquals" }, - - { K_K0, "k0" }, - { K_K1, "k1" }, - { K_K2, "k2" }, - { K_K3, "k3" }, - { K_K4, "k4" }, - { K_K5, "k5" }, - { K_K6, "k6" }, - { K_K7, "k7" }, - { K_K8, "k8" }, - { K_K9, "k9" }, - - { '<', "lt" }, - - { K_MOUSE, "Mouse" }, - { K_LEFTMOUSE, "LeftMouse" }, - { K_LEFTMOUSE_NM, "LeftMouseNM" }, - { K_LEFTDRAG, "LeftDrag" }, - { K_LEFTRELEASE, "LeftRelease" }, - { K_LEFTRELEASE_NM, "LeftReleaseNM" }, - { K_MOUSEMOVE, "MouseMove" }, - { K_MIDDLEMOUSE, "MiddleMouse" }, - { K_MIDDLEDRAG, "MiddleDrag" }, - { K_MIDDLERELEASE, "MiddleRelease" }, - { K_RIGHTMOUSE, "RightMouse" }, - { K_RIGHTDRAG, "RightDrag" }, - { K_RIGHTRELEASE, "RightRelease" }, - { K_MOUSEDOWN, "ScrollWheelUp" }, - { K_MOUSEUP, "ScrollWheelDown" }, - { K_MOUSELEFT, "ScrollWheelRight" }, - { K_MOUSERIGHT, "ScrollWheelLeft" }, - { K_MOUSEDOWN, "MouseDown" }, // OBSOLETE: Use - { K_MOUSEUP, "MouseUp" }, // ScrollWheelXXX instead - { K_X1MOUSE, "X1Mouse" }, - { K_X1DRAG, "X1Drag" }, - { K_X1RELEASE, "X1Release" }, - { K_X2MOUSE, "X2Mouse" }, - { K_X2DRAG, "X2Drag" }, - { K_X2RELEASE, "X2Release" }, - { K_DROP, "Drop" }, - { K_ZERO, "Nul" }, - { K_SNR, "SNR" }, - { K_PLUG, "Plug" }, - { K_IGNORE, "Ignore" }, - { K_COMMAND, "Cmd" }, - { 0, NULL } - // NOTE: When adding a long name update MAX_KEY_NAME_LEN. -}; - static struct mousetable { int pseudo_code; // Code for pseudo mouse event int button; // Which mouse button is it? @@ -544,15 +338,18 @@ char *get_special_key_name(int c, int modifiers) } } } else { // use name of special key - size_t len = strlen(key_names_table[table_idx].name); + const String *s = key_names_table[table_idx].pref_name != NULL + ? key_names_table[table_idx].pref_name + : &key_names_table[table_idx].name; - if ((int)len + idx + 2 <= MAX_KEY_NAME_LEN) { - STRCPY(string + idx, key_names_table[table_idx].name); - idx += (int)len; + if ((int)s->size + idx + 2 <= MAX_KEY_NAME_LEN) { + STRCPY(string + idx, s->data); + idx += (int)s->size; } } string[idx++] = '>'; string[idx] = NUL; + return string; } @@ -795,17 +592,13 @@ static int extract_modifiers(int key, int *modp, const bool simplify, bool *cons /// @return the index when found, -1 when not found. int find_special_key_in_table(int c) { - int i; - - for (i = 0; key_names_table[i].name != NULL; i++) { + for (int i = 0; i < (int)ARRAY_SIZE(key_names_table); i++) { if (c == key_names_table[i].key) { - break; + return i; } } - if (key_names_table[i].name == NULL) { - i = -1; - } - return i; + + return -1; } /// Find the special key with the given name @@ -823,20 +616,13 @@ int get_special_key_code(const char *name) return TERMCAP2KEY((uint8_t)name[2], (uint8_t)name[3]); } - for (int i = 0; key_names_table[i].name != NULL; i++) { - const char *const table_name = key_names_table[i].name; - int j; - for (j = 0; ascii_isident((uint8_t)name[j]) && table_name[j] != NUL; j++) { - if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC((uint8_t)name[j])) { - break; - } - } - if (!ascii_isident((uint8_t)name[j]) && table_name[j] == NUL) { - return key_names_table[i].key; - } + const char *name_end = name; + while (ascii_isident(*name_end)) { + name_end++; } - return 0; + int idx = get_special_key_code_hash(name, (size_t)(name_end - name)); + return idx >= 0 ? key_names_table[idx].key : 0; } /// Look up the given mouse code to return the relevant information in the other arguments. diff --git a/src/nvim/keycodes.lua b/src/nvim/keycodes.lua new file mode 100644 index 0000000000..04f09eb005 --- /dev/null +++ b/src/nvim/keycodes.lua @@ -0,0 +1,208 @@ +return { + --- @type [string, string][] List of [key, name] tuples. + --- For keys with multiple names, put the preferred name first. + --- For multiple keys with the same name, put the preferred key first. + names = { + { [[' ']], 'Space' }, + { [[TAB]], 'Tab' }, + { [[K_TAB]], 'Tab' }, + { [[NL]], 'NL' }, + { [[NL]], 'NewLine' }, -- Alternative name + { [[NL]], 'LineFeed' }, -- Alternative name + { [[NL]], 'LF' }, -- Alternative name + { [[CAR]], 'CR' }, + { [[CAR]], 'Return' }, -- Alternative name + { [[CAR]], 'Enter' }, -- Alternative name + { [[K_BS]], 'BS' }, + { [[K_BS]], 'BackSpace' }, -- Alternative name + { [[ESC]], 'Esc' }, + { [[ESC]], 'Escape' }, -- Alternative name + { [[CSI]], 'CSI' }, + { [['|']], 'Bar' }, + { [['\\']], 'Bslash' }, + { [[K_DEL]], 'Del' }, + { [[K_DEL]], 'Delete' }, -- Alternative name + { [[K_KDEL]], 'kDel' }, + { [[K_KDEL]], 'KPPeriod' }, -- libtermkey name + { [[K_UP]], 'Up' }, + { [[K_DOWN]], 'Down' }, + { [[K_LEFT]], 'Left' }, + { [[K_RIGHT]], 'Right' }, + { [[K_XUP]], 'xUp' }, + { [[K_XDOWN]], 'xDown' }, + { [[K_XLEFT]], 'xLeft' }, + { [[K_XRIGHT]], 'xRight' }, + { [[K_KUP]], 'kUp' }, + { [[K_KUP]], 'KP8' }, + { [[K_KDOWN]], 'kDown' }, + { [[K_KDOWN]], 'KP2' }, + { [[K_KLEFT]], 'kLeft' }, + { [[K_KLEFT]], 'KP4' }, + { [[K_KRIGHT]], 'kRight' }, + { [[K_KRIGHT]], 'KP6' }, + + { [[K_F1]], 'F1' }, + { [[K_F2]], 'F2' }, + { [[K_F3]], 'F3' }, + { [[K_F4]], 'F4' }, + { [[K_F5]], 'F5' }, + { [[K_F6]], 'F6' }, + { [[K_F7]], 'F7' }, + { [[K_F8]], 'F8' }, + { [[K_F9]], 'F9' }, + { [[K_F10]], 'F10' }, + + { [[K_F11]], 'F11' }, + { [[K_F12]], 'F12' }, + { [[K_F13]], 'F13' }, + { [[K_F14]], 'F14' }, + { [[K_F15]], 'F15' }, + { [[K_F16]], 'F16' }, + { [[K_F17]], 'F17' }, + { [[K_F18]], 'F18' }, + { [[K_F19]], 'F19' }, + { [[K_F20]], 'F20' }, + + { [[K_F21]], 'F21' }, + { [[K_F22]], 'F22' }, + { [[K_F23]], 'F23' }, + { [[K_F24]], 'F24' }, + { [[K_F25]], 'F25' }, + { [[K_F26]], 'F26' }, + { [[K_F27]], 'F27' }, + { [[K_F28]], 'F28' }, + { [[K_F29]], 'F29' }, + { [[K_F30]], 'F30' }, + + { [[K_F31]], 'F31' }, + { [[K_F32]], 'F32' }, + { [[K_F33]], 'F33' }, + { [[K_F34]], 'F34' }, + { [[K_F35]], 'F35' }, + { [[K_F36]], 'F36' }, + { [[K_F37]], 'F37' }, + { [[K_F38]], 'F38' }, + { [[K_F39]], 'F39' }, + { [[K_F40]], 'F40' }, + + { [[K_F41]], 'F41' }, + { [[K_F42]], 'F42' }, + { [[K_F43]], 'F43' }, + { [[K_F44]], 'F44' }, + { [[K_F45]], 'F45' }, + { [[K_F46]], 'F46' }, + { [[K_F47]], 'F47' }, + { [[K_F48]], 'F48' }, + { [[K_F49]], 'F49' }, + { [[K_F50]], 'F50' }, + + { [[K_F51]], 'F51' }, + { [[K_F52]], 'F52' }, + { [[K_F53]], 'F53' }, + { [[K_F54]], 'F54' }, + { [[K_F55]], 'F55' }, + { [[K_F56]], 'F56' }, + { [[K_F57]], 'F57' }, + { [[K_F58]], 'F58' }, + { [[K_F59]], 'F59' }, + { [[K_F60]], 'F60' }, + + { [[K_F61]], 'F61' }, + { [[K_F62]], 'F62' }, + { [[K_F63]], 'F63' }, + + { [[K_XF1]], 'xF1' }, + { [[K_XF2]], 'xF2' }, + { [[K_XF3]], 'xF3' }, + { [[K_XF4]], 'xF4' }, + + { [[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' }, + { [[K_KINS]], 'KP0' }, + { [[K_HOME]], 'Home' }, + { [[K_KHOME]], 'kHome' }, + { [[K_KHOME]], 'KP7' }, + { [[K_XHOME]], 'xHome' }, + { [[K_ZHOME]], 'zHome' }, + { [[K_END]], 'End' }, + { [[K_KEND]], 'kEnd' }, + { [[K_KEND]], 'KP1' }, + { [[K_XEND]], 'xEnd' }, + { [[K_ZEND]], 'zEnd' }, + { [[K_PAGEUP]], 'PageUp' }, + { [[K_PAGEDOWN]], 'PageDown' }, + { [[K_KPAGEUP]], 'kPageUp' }, + { [[K_KPAGEUP]], 'KP9' }, + { [[K_KPAGEDOWN]], 'kPageDown' }, + { [[K_KPAGEDOWN]], 'KP3' }, + { [[K_KORIGIN]], 'kOrigin' }, + { [[K_KORIGIN]], 'KP5' }, + + { [[K_KPLUS]], 'kPlus' }, + { [[K_KPLUS]], 'KPPlus' }, + { [[K_KMINUS]], 'kMinus' }, + { [[K_KMINUS]], 'KPMinus' }, + { [[K_KDIVIDE]], 'kDivide' }, + { [[K_KDIVIDE]], 'KPDiv' }, + { [[K_KMULTIPLY]], 'kMultiply' }, + { [[K_KMULTIPLY]], 'KPMult' }, + { [[K_KENTER]], 'kEnter' }, + { [[K_KENTER]], 'KPEnter' }, + { [[K_KPOINT]], 'kPoint' }, + { [[K_KCOMMA]], 'kComma' }, + { [[K_KCOMMA]], 'KPComma' }, + { [[K_KEQUAL]], 'kEqual' }, + { [[K_KEQUAL]], 'KPEquals' }, + + { [[K_K0]], 'k0' }, + { [[K_K1]], 'k1' }, + { [[K_K2]], 'k2' }, + { [[K_K3]], 'k3' }, + { [[K_K4]], 'k4' }, + { [[K_K5]], 'k5' }, + { [[K_K6]], 'k6' }, + { [[K_K7]], 'k7' }, + { [[K_K8]], 'k8' }, + { [[K_K9]], 'k9' }, + + { [['<']], 'lt' }, + + { [[K_MOUSE]], 'Mouse' }, + { [[K_LEFTMOUSE]], 'LeftMouse' }, + { [[K_LEFTMOUSE_NM]], 'LeftMouseNM' }, + { [[K_LEFTDRAG]], 'LeftDrag' }, + { [[K_LEFTRELEASE]], 'LeftRelease' }, + { [[K_LEFTRELEASE_NM]], 'LeftReleaseNM' }, + { [[K_MOUSEMOVE]], 'MouseMove' }, + { [[K_MIDDLEMOUSE]], 'MiddleMouse' }, + { [[K_MIDDLEDRAG]], 'MiddleDrag' }, + { [[K_MIDDLERELEASE]], 'MiddleRelease' }, + { [[K_RIGHTMOUSE]], 'RightMouse' }, + { [[K_RIGHTDRAG]], 'RightDrag' }, + { [[K_RIGHTRELEASE]], 'RightRelease' }, + { [[K_MOUSEDOWN]], 'ScrollWheelUp' }, + { [[K_MOUSEUP]], 'ScrollWheelDown' }, + { [[K_MOUSELEFT]], 'ScrollWheelRight' }, + { [[K_MOUSERIGHT]], 'ScrollWheelLeft' }, + { [[K_MOUSEDOWN]], 'MouseDown' }, -- OBSOLETE: Use ScrollWheelUp instead + { [[K_MOUSEUP]], 'MouseUp' }, -- OBSOLETE: Use ScrollWheelDown instead + { [[K_X1MOUSE]], 'X1Mouse' }, + { [[K_X1DRAG]], 'X1Drag' }, + { [[K_X1RELEASE]], 'X1Release' }, + { [[K_X2MOUSE]], 'X2Mouse' }, + { [[K_X2DRAG]], 'X2Drag' }, + { [[K_X2RELEASE]], 'X2Release' }, + { [[K_DROP]], 'Drop' }, + { [[K_ZERO]], 'Nul' }, + { [[K_SNR]], 'SNR' }, + { [[K_PLUG]], 'Plug' }, + { [[K_IGNORE]], 'Ignore' }, + { [[K_COMMAND]], 'Cmd' }, + -- NOTE: When adding a long name update MAX_KEY_NAME_LEN. + }, +} diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index b1766db379..724a3d77e6 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -125,30 +125,36 @@ lua_State *get_global_lstate(void) return global_lstate; } -/// Convert lua error into a Vim error message +/// Gets the Lua error at top of stack as a string, possibly modifying it in-place (but doesn't +/// change stack height). /// -/// @param lstate Lua interpreter state. -/// @param[in] msg Message base, must contain one `%.*s`. -void nlua_error(lua_State *const lstate, const char *const msg) - FUNC_ATTR_NONNULL_ALL +/// The returned string points to memory on the Lua stack. Use or duplicate it before using +/// `lstate` again. +/// +/// @param[out] len length of error (can be NULL) +static const char *nlua_get_error(lua_State *lstate, size_t *len) { - size_t len; - const char *str = NULL; - if (luaL_getmetafield(lstate, -1, "__tostring")) { if (lua_isfunction(lstate, -1) && luaL_callmeta(lstate, -2, "__tostring")) { - // call __tostring, convert the result and pop result. - str = lua_tolstring(lstate, -1, &len); - lua_pop(lstate, 1); + // call __tostring, convert the result and replace error with it + lua_replace(lstate, -3); } // pop __tostring. lua_pop(lstate, 1); } - if (!str) { - // defer to lua default conversion, this will render tables as [NULL]. - str = lua_tolstring(lstate, -1, &len); - } + return lua_tolstring(lstate, -1, len); +} + +/// Converts a Lua error into a Vim error message. +/// +/// @param lstate Lua interpreter state. +/// @param[in] msg Message base, must contain one `%.*s`. +void nlua_error(lua_State *const lstate, const char *const msg) + FUNC_ATTR_NONNULL_ALL +{ + size_t len; + const char *str = nlua_get_error(lstate, &len); if (in_script) { fprintf(stderr, msg, (int)len, str); @@ -218,7 +224,9 @@ static int nlua_fast_cfpcall(lua_State *lstate, int nargs, int nresult, int flag // consider out of memory errors unrecoverable, just like xmalloc() preserve_exit(e_outofmem); } - const char *error = lua_tostring(lstate, -1); + + size_t len; + const char *error = nlua_get_error(lstate, &len); multiqueue_put(main_loop.events, nlua_luv_error_event, error != NULL ? xstrdup(error) : NULL, (void *)(intptr_t)kCallback); @@ -1659,13 +1667,13 @@ void ex_lua(exarg_T *const eap) // ":lua {code}", ":={expr}" or ":lua ={expr}" // - // When "=expr" is used transform it to "vim.print(expr)". + // When "=expr" is used transform it to "vim._print(true, expr)". if (eap->cmdidx == CMD_equal || code[0] == '=') { size_t off = (eap->cmdidx == CMD_equal) ? 0 : 1; - len += sizeof("vim.print()") - 1 - off; + len += sizeof("vim._print(true, )") - 1 - off; // `nlua_typval_exec` doesn't expect NUL-terminated string so `len` must end before NUL byte. char *code_buf = xmallocz(len); - vim_snprintf(code_buf, len + 1, "vim.print(%s)", code + off); + vim_snprintf(code_buf, len + 1, "vim._print(true, %s)", code + off); xfree(code); code = code_buf; } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 44e48e9e72..83ab0d5c24 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1524,6 +1524,7 @@ static void set_vcount_ca(cmdarg_T *cap, bool *set_prevcount) /// do_pending_operator(). void end_visual_mode(void) { + VIsual_select_exclu_adj = false; VIsual_active = false; setmouse(); mouse_dragging = 0; @@ -4035,17 +4036,24 @@ static int normal_search(cmdarg_T *cap, int dir, char *pat, size_t patlen, int o /// cap->nchar is NUL for ',' and ';' (repeat the search) static void nv_csearch(cmdarg_T *cap) { - bool t_cmd; + bool cursor_dec = false; - if (cap->cmdchar == 't' || cap->cmdchar == 'T') { - t_cmd = true; - } else { - t_cmd = false; + // If adjusted cursor position previously, unadjust it. + if (*p_sel == 'e' && VIsual_active && VIsual_mode == 'v' + && VIsual_select_exclu_adj) { + unadjust_for_sel(); + cursor_dec = true; } + bool t_cmd = cap->cmdchar == 't' || cap->cmdchar == 'T'; + cap->oap->motion_type = kMTCharWise; if (IS_SPECIAL(cap->nchar) || searchc(cap, t_cmd) == false) { clearopbeep(cap->oap); + // Revert unadjust when failed. + if (cursor_dec) { + adjust_for_sel(cap); + } return; } @@ -5054,6 +5062,8 @@ static void nv_visual(cmdarg_T *cap) n_start_visual_mode(cap->cmdchar); if (VIsual_mode != 'V' && *p_sel == 'e') { cap->count1++; // include one more char + } else { + VIsual_select_exclu_adj = false; } if (cap->count0 > 0 && --cap->count1 > 0) { // With a count select that many characters or lines. @@ -6021,6 +6031,7 @@ static void adjust_for_sel(cmdarg_T *cap) && gchar_cursor() != NUL && lt(VIsual, curwin->w_cursor)) { inc_cursor(); cap->oap->inclusive = false; + VIsual_select_exclu_adj = true; } } @@ -6042,7 +6053,7 @@ bool unadjust_for_sel(void) /// @return true when backed up to the previous line. bool unadjust_for_sel_inner(pos_T *pp) { - colnr_T cs, ce; + VIsual_select_exclu_adj = false; if (pp->coladd > 0) { pp->coladd--; @@ -6050,6 +6061,7 @@ bool unadjust_for_sel_inner(pos_T *pp) pp->col--; mark_mb_adjustpos(curbuf, pp); if (virtual_active(curwin)) { + colnr_T cs, ce; getvcol(curwin, pp, &cs, NULL, &ce); pp->coladd = ce - cs; } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 72c75534d4..0bda14402f 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -2806,6 +2806,10 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) curbuf->b_op_start.col = 0; curbuf->b_op_end.col = MAXCOL; } + if (yank_type != kMTLineWise && !oap->inclusive) { + // Exclude the end position. + decl(&curbuf->b_op_end); + } } } diff --git a/src/nvim/options.lua b/src/nvim/options.lua index d1d5a56082..49de8b92ef 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1525,15 +1525,15 @@ local options = { match, e.g., what file it comes from. noinsert Do not insert any text for a match until the user selects - a match from the menu. Only works in combination with + a match from the menu. Only works in combination with "menu" or "menuone". No effect if "longest" is present. noselect Same as "noinsert", except that no menu item is - pre-selected. If both "noinsert" and "noselect" are + pre-selected. If both "noinsert" and "noselect" are present, "noselect" has precedence. nosort Disable sorting of completion candidates based on fuzzy - scores when "fuzzy" is enabled. Candidates will appear + scores when "fuzzy" is enabled. Candidates will appear in their original order. popup Show extra information about the currently selected @@ -1544,7 +1544,7 @@ local options = { Preinsert the portion of the first candidate word that is not part of the current completion leader and using the |hl-ComplMatchIns| highlight group. In order for it to - work, "fuzzy" must not bet set and "menuone" must be set. + work, "fuzzy" must not be set and "menuone" must be set. preview Show extra information about the currently selected completion in the preview window. Only works in @@ -6236,7 +6236,7 @@ local options = { set path=,, < - A directory name may end in a ':' or '/'. - Environment variables are expanded |:set_env|. - - When using |netrw.vim| URLs can be used. For example, adding + - When using |netrw| URLs can be used. For example, adding "https://www.vim.org" will make ":find index.html" work. - Search upwards and downwards in a directory tree using "*", "**" and ";". See |file-searching| for info and syntax. @@ -10020,7 +10020,7 @@ local options = { cb = 'did_set_wildmode', defaults = 'full', -- Keep this in sync with check_opt_wim(). - values = { 'full', 'longest', 'list', 'lastused' }, + values = { 'full', 'longest', 'list', 'lastused', 'noselect' }, flags = true, deny_duplicates = false, desc = [=[ @@ -10042,7 +10042,10 @@ local options = { "lastused" When completing buffer names and more than one buffer matches, sort buffers by time last used (other than the current buffer). - When there is only a single match, it is fully completed in all cases. + "noselect" Do not pre-select first menu item and start 'wildmenu' + if it is enabled. + When there is only a single match, it is fully completed in all cases + except when "noselect" is present. Examples of useful colon-separated values: "longest:full" Like "longest", but also start 'wildmenu' if it is @@ -10065,7 +10068,11 @@ local options = { set wildmode=list,full < List all matches without completing, then each full match >vim set wildmode=longest,list - < Complete longest common string, then list alternatives. + < Complete longest common string, then list alternatives >vim + set wildmode=noselect:full + < Display 'wildmenu' without completing, then each full match >vim + set wildmode=noselect:lastused,full + < Same as above, but sort buffers by time last used. More info here: |cmdline-completion|. ]=], full_name = 'wildmode', diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 2e533bea23..b057cae08d 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -430,12 +430,10 @@ static int *pum_compute_text_attrs(char *text, hlf_T hlf, int user_hlattr) size_t leader_len = strlen(leader); garray_T *ga = NULL; - bool matched_start = false; + int matched_len = -1; if (in_fuzzy) { ga = fuzzy_match_str_with_pos(text, leader); - } else { - matched_start = mb_strnicmp(text, leader, leader_len) == 0; } const char *ptr = text; @@ -456,10 +454,16 @@ static int *pum_compute_text_attrs(char *text, hlf_T hlf, int user_hlattr) break; } } - } else if (matched_start && ptr < text + leader_len) { - new_attr = win_hl_attr(curwin, is_select ? HLF_PMSI : HLF_PMNI); - new_attr = hl_combine_attr(win_hl_attr(curwin, HLF_PMNI), new_attr); - new_attr = hl_combine_attr(win_hl_attr(curwin, (int)hlf), new_attr); + } else { + if (matched_len < 0 && mb_strnicmp(ptr, leader, leader_len) == 0) { + matched_len = (int)leader_len; + } + if (matched_len > 0) { + new_attr = win_hl_attr(curwin, is_select ? HLF_PMSI : HLF_PMNI); + new_attr = hl_combine_attr(win_hl_attr(curwin, HLF_PMNI), new_attr); + new_attr = hl_combine_attr(win_hl_attr(curwin, (int)hlf), new_attr); + matched_len--; + } } new_attr = hl_combine_attr(win_hl_attr(curwin, HLF_PNI), new_attr); @@ -799,14 +803,25 @@ static void pum_preview_set_text(buf_T *buf, char *info, linenr_T *lnum, int *ma Error err = ERROR_INIT; Arena arena = ARENA_EMPTY; Array replacement = ARRAY_DICT_INIT; - char *token = NULL; - char *line = os_strtok(info, "\n", &token); buf->b_p_ma = true; - while (line != NULL) { - ADD(replacement, STRING_OBJ(cstr_to_string(line))); + + // Iterate through the string line by line by temporarily replacing newlines with NUL + for (char *curr = info, *next; curr; curr = next ? next + 1 : NULL) { + if ((next = strchr(curr, '\n'))) { + *next = NUL; // Temporarily replace the newline with a string terminator + } + // Only skip if this is an empty line AND it's the last line + if (*curr == '\0' && !next) { + break; + } + + *max_width = MAX(*max_width, (int)mb_string2cells(curr)); + ADD(replacement, STRING_OBJ(cstr_to_string(curr))); (*lnum)++; - (*max_width) = MAX(*max_width, (int)mb_string2cells(line)); - line = os_strtok(NULL, "\n", &token); + + if (next) { + *next = '\n'; + } } int original_textlock = textlock; @@ -824,8 +839,23 @@ static void pum_preview_set_text(buf_T *buf, char *info, linenr_T *lnum, int *ma buf->b_p_ma = false; } +/// Calculate the total height (in screen lines) of the first 'count' buffer lines in window 'wp'. +/// Takes line wrapping and other display factors into account. +/// +/// @param wp Window pointer +/// @param count Number of buffer lines to measure (1-based) +/// @return Total height in screen lines +static inline int pum_preview_win_height(win_T *wp, linenr_T count) +{ + int height = 0; + for (int i = 1; i <= count; i++) { + height += plines_win(wp, i, false); + } + return height; +} + /// adjust floating info preview window position -static void pum_adjust_info_position(win_T *wp, int height, int width) +static void pum_adjust_info_position(win_T *wp, int width) { int col = pum_col + pum_width + pum_scrollbar + 1; // TODO(glepnir): support config align border by using completepopup @@ -846,8 +876,10 @@ static void pum_adjust_info_position(win_T *wp, int height, int width) } // when pum_above is SW otherwise is NW wp->w_config.anchor = pum_above ? kFloatAnchorSouth : 0; - wp->w_config.row = pum_above ? pum_row + height : pum_row; - wp->w_config.height = MIN(Rows, height); + linenr_T count = wp->w_buffer->b_ml.ml_line_count; + wp->w_width_inner = wp->w_config.width; + wp->w_config.height = MIN(Rows, pum_preview_win_height(wp, count)); + wp->w_config.row = pum_above ? pum_row + wp->w_config.height : pum_row; wp->w_config.hide = false; win_config_float(wp, wp->w_config); } @@ -880,10 +912,7 @@ win_T *pum_set_info(int selected, char *info) RedrawingDisabled--; redraw_later(wp, UPD_NOT_VALID); - if (wp->w_p_wrap) { - lnum += plines_win(wp, lnum, true); - } - pum_adjust_info_position(wp, lnum, max_info_width); + pum_adjust_info_position(wp, max_info_width); unblock_autocmds(); return wp; } @@ -1059,11 +1088,8 @@ static bool pum_set_selected(int n, int repeat) curwin->w_cursor.col = 0; if (use_float) { - if (curwin->w_p_wrap) { - lnum += plines_win(curwin, lnum, true); - } // adjust floating window by actually height and max info text width - pum_adjust_info_position(curwin, lnum, max_info_width); + pum_adjust_info_position(curwin, max_info_width); } if ((curwin != curwin_save && win_valid(curwin_save)) diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 322b97d6bf..add4c9dcb6 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -475,6 +475,28 @@ bool striequal(const char *a, const char *b) return (a == NULL && b == NULL) || (a && b && STRICMP(a, b) == 0); } +/// Compare two ASCII strings, for length "len", ignoring case, ignoring locale. +/// +/// @return 0 for match, < 0 for smaller, > 0 for bigger +int vim_strnicmp_asc(const char *s1, const char *s2, size_t len) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + int i = 0; + while (len > 0) { + i = TOLOWER_ASC(*s1) - TOLOWER_ASC(*s2); + if (i != 0) { + break; // this character is different + } + if (*s1 == NUL) { + break; // strings match until NUL + } + s1++; + s2++; + len--; + } + return i; +} + /// strchr() version which handles multibyte strings /// /// @param[in] string String to search in. diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 557d41a467..baa862f57a 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -2986,6 +2986,8 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, bool keep_help) secure = 1; sandbox++; curwin->w_cursor.lnum = 1; // start command in line 1 + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; do_cmdline_cmd(pbuf); retval = OK; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 47630ddea9..f4acfb486c 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -106,10 +106,11 @@ typedef struct { VimState state; Terminal *term; - int save_rd; // saved value of RedrawingDisabled + int save_rd; ///< saved value of RedrawingDisabled bool close; - bool got_bsl; // if the last input was <C-\> - bool got_bsl_o; // if left terminal mode with <c-\><c-o> + bool got_bsl; ///< if the last input was <C-\> + bool got_bsl_o; ///< if left terminal mode with <c-\><c-o> + bool cursor_visible; ///< cursor's current visibility; ensures matched busy_start/stop UI events } TerminalState; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -169,7 +170,8 @@ struct terminal { struct { int row, col; int shape; - bool visible; + bool visible; ///< Terminal wants to show cursor. + ///< `TerminalState.cursor_visible` indicates whether it is actually shown. bool blink; } cursor; @@ -177,6 +179,7 @@ struct terminal { bool resize; ///< pending width/height bool cursor; ///< pending cursor shape or blink change StringBuilder *send; ///< When there is a pending TermRequest autocommand, block and store input. + MultiQueue *events; ///< Events waiting for refresh. } pending; bool theme_updates; ///< Send a theme update notification when 'bg' changes @@ -186,7 +189,7 @@ struct terminal { char *selection_buffer; ///< libvterm selection buffer StringBuilder selection; ///< Growable array containing full selection data - StringBuilder termrequest_buffer; ///< Growable array containing unfinished request payload + StringBuilder termrequest_buffer; ///< Growable array containing unfinished request sequence size_t refcount; // reference count }; @@ -213,16 +216,37 @@ static Set(ptr_t) invalidated_terminals = SET_INIT; static void emit_termrequest(void **argv) { Terminal *term = argv[0]; - char *payload = argv[1]; - size_t payload_length = (size_t)argv[2]; + char *sequence = argv[1]; + size_t sequence_length = (size_t)argv[2]; StringBuilder *pending_send = argv[3]; + int row = (int)(intptr_t)argv[4]; + int col = (int)(intptr_t)argv[5]; + + if (term->sb_pending > 0) { + // Don't emit the event while there is pending scrollback because we need + // the buffer contents to be fully updated. If this is the case, schedule + // the event onto the pending queue where it will be executed after the + // terminal is refreshed and the pending scrollback is cleared. + multiqueue_put(term->pending.events, emit_termrequest, term, sequence, (void *)sequence_length, + pending_send, (void *)(intptr_t)row, (void *)(intptr_t)col); + return; + } + + set_vim_var_string(VV_TERMREQUEST, sequence, (ptrdiff_t)sequence_length); + + MAXSIZE_TEMP_ARRAY(cursor, 2); + ADD_C(cursor, INTEGER_OBJ(row)); + ADD_C(cursor, INTEGER_OBJ(col)); + + MAXSIZE_TEMP_DICT(data, 2); + String termrequest = { .data = sequence, .size = sequence_length }; + PUT_C(data, "sequence", STRING_OBJ(termrequest)); + PUT_C(data, "cursor", ARRAY_OBJ(cursor)); buf_T *buf = handle_get_buffer(term->buf_handle); - String termrequest = { .data = payload, .size = payload_length }; - Object data = STRING_OBJ(termrequest); - set_vim_var_string(VV_TERMREQUEST, payload, (ptrdiff_t)payload_length); - apply_autocmds_group(EVENT_TERMREQUEST, NULL, NULL, false, AUGROUP_ALL, buf, NULL, &data); - xfree(payload); + apply_autocmds_group(EVENT_TERMREQUEST, NULL, NULL, true, AUGROUP_ALL, buf, NULL, + &DICT_OBJ(data)); + xfree(sequence); StringBuilder *term_pending_send = term->pending.send; term->pending.send = NULL; @@ -236,12 +260,15 @@ static void emit_termrequest(void **argv) xfree(pending_send); } -static void schedule_termrequest(Terminal *term, char *payload, size_t payload_length) +static void schedule_termrequest(Terminal *term, char *sequence, size_t sequence_length) { term->pending.send = xmalloc(sizeof(StringBuilder)); kv_init(*term->pending.send); - multiqueue_put(main_loop.events, emit_termrequest, term, payload, (void *)payload_length, - term->pending.send); + + int line = row_to_linenr(term, term->cursor.row); + multiqueue_put(main_loop.events, emit_termrequest, term, sequence, (void *)sequence_length, + term->pending.send, (void *)(intptr_t)line, + (void *)(intptr_t)term->cursor.col); } static int parse_osc8(VTermStringFragment frag, int *attr) @@ -315,8 +342,8 @@ static int on_osc(int command, VTermStringFragment frag, void *user) } kv_concat_len(term->termrequest_buffer, frag.str, frag.len); if (frag.final) { - char *payload = xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size); - schedule_termrequest(user, payload, term->termrequest_buffer.size); + char *sequence = xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size); + schedule_termrequest(user, sequence, term->termrequest_buffer.size); } return 1; } @@ -338,8 +365,8 @@ static int on_dcs(const char *command, size_t commandlen, VTermStringFragment fr } kv_concat_len(term->termrequest_buffer, frag.str, frag.len); if (frag.final) { - char *payload = xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size); - schedule_termrequest(user, payload, term->termrequest_buffer.size); + char *sequence = xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size); + schedule_termrequest(user, sequence, term->termrequest_buffer.size); } return 1; } @@ -361,8 +388,8 @@ static int on_apc(VTermStringFragment frag, void *user) } kv_concat_len(term->termrequest_buffer, frag.str, frag.len); if (frag.final) { - char *payload = xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size); - schedule_termrequest(user, payload, term->termrequest_buffer.size); + char *sequence = xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size); + schedule_termrequest(user, sequence, term->termrequest_buffer.size); } return 1; } @@ -466,6 +493,13 @@ void terminal_open(Terminal **termpp, buf_T *buf, TerminalOptions opts) term->invalid_start = 0; term->invalid_end = opts.height; + // Create a separate queue for events which need to wait for a terminal + // refresh. We cannot reschedule events back onto the main queue because this + // can create an infinite loop (#32753). + // This queue is never processed directly: when the terminal is refreshed, all + // events from this queue are copied back onto the main event queue. + term->pending.events = multiqueue_new(NULL, NULL); + aco_save_T aco; aucmd_prepbuf(&aco, buf); @@ -650,6 +684,7 @@ bool terminal_enter(void) assert(buf->terminal); // Should only be called when curbuf has a terminal. TerminalState s[1] = { 0 }; s->term = buf->terminal; + s->cursor_visible = true; // Assume visible; may change via refresh_cursor later. stop_insert_mode = false; // Ensure the terminal is properly sized. Ideally window size management @@ -662,10 +697,6 @@ 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 @@ -692,10 +723,7 @@ bool terminal_enter(void) curwin->w_p_so = 0; curwin->w_p_siso = 0; - // Update the cursor shape table and flush changes to the UI - s->term->pending.cursor = true; - refresh_cursor(s->term); - + s->term->pending.cursor = true; // Update the cursor shape table adjust_topline(s->term, buf, 0); // scroll to end showmode(); curwin->w_redr_status = true; // For mode() in statusline. #8323 @@ -716,8 +744,8 @@ 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. + if (!s->cursor_visible) { + // If cursor was hidden, show it again. Do so right after restoring State. ui_busy_stop(); } @@ -782,10 +810,13 @@ static void terminal_check_cursor(void) // 0 if the main loop must exit static int terminal_check(VimState *state) { + TerminalState *const s = (TerminalState *)state; + if (stop_insert_mode) { return 0; } + assert(s->term == curbuf->terminal); terminal_check_cursor(); validate_cursor(curwin); @@ -812,6 +843,7 @@ static int terminal_check(VimState *state) } setcursor(); + refresh_cursor(s->term, &s->cursor_visible); ui_flush(); return 1; } @@ -914,23 +946,9 @@ static int terminal_execute(VimState *state, int key) } if (s->term != curbuf->terminal) { // Active terminal buffer changed, flush terminal's cursor state to the UI - curbuf->terminal->pending.cursor = true; - - if (!s->term->cursor.visible) { - // If cursor was hidden, show it again - ui_busy_stop(); - } - - if (!curbuf->terminal->cursor.visible) { - // Hide cursor if it should be hidden - ui_busy_start(); - } - - invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1); - invalidate_terminal(curbuf->terminal, - curbuf->terminal->cursor.row, - curbuf->terminal->cursor.row + 1); s->term = curbuf->terminal; + s->term->pending.cursor = true; + invalidate_terminal(s->term, -1, -1); } return 1; } @@ -964,6 +982,7 @@ void terminal_destroy(Terminal **termpp) kv_destroy(term->selection); kv_destroy(term->termrequest_buffer); vterm_free(term->vt); + multiqueue_free(term->pending.events); xfree(term); *termpp = NULL; // coverity[dead-store] } @@ -1268,17 +1287,8 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data) break; case VTERM_PROP_CURSORVISIBLE: - if (is_focused(term)) { - if (!val->boolean && term->cursor.visible) { - // Hide the cursor - ui_busy_start(); - } else if (val->boolean && !term->cursor.visible) { - // Unhide the cursor - ui_busy_stop(); - } - invalidate_terminal(term, -1, -1); - } term->cursor.visible = val->boolean; + invalidate_terminal(term, -1, -1); break; case VTERM_PROP_TITLE: { @@ -2019,23 +2029,38 @@ static void refresh_terminal(Terminal *term) } linenr_T ml_before = buf->b_ml.ml_line_count; - // refresh_ functions assume the terminal buffer is current + // Some refresh_ functions assume the terminal buffer is current. Don't call refresh_cursor here, + // as we don't want a terminal that was possibly made temporarily current influencing the cursor. aco_save_T aco; aucmd_prepbuf(&aco, buf); refresh_size(term, buf); refresh_scrollback(term, buf); refresh_screen(term, buf); - refresh_cursor(term); aucmd_restbuf(&aco); int ml_added = buf->b_ml.ml_line_count - ml_before; adjust_topline(term, buf, ml_added); + + // Copy pending events back to the main event queue + multiqueue_move_events(main_loop.events, term->pending.events); } -static void refresh_cursor(Terminal *term) +static void refresh_cursor(Terminal *term, bool *cursor_visible) FUNC_ATTR_NONNULL_ALL { - if (!is_focused(term) || !term->pending.cursor) { + if (!is_focused(term)) { + return; + } + if (term->cursor.visible != *cursor_visible) { + *cursor_visible = term->cursor.visible; + if (*cursor_visible) { + ui_busy_stop(); + } else { + ui_busy_start(); + } + } + + if (!term->pending.cursor) { return; } term->pending.cursor = false; @@ -2143,6 +2168,7 @@ static void adjust_scrollback(Terminal *term, buf_T *buf) // Refresh the scrollback of an invalidated terminal. static void refresh_scrollback(Terminal *term, buf_T *buf) { + assert(buf == curbuf); // TODO(seandewar): remove this condition int width, height; vterm_get_size(term->vt, &height, &width); diff --git a/src/nvim/textobject.c b/src/nvim/textobject.c index e3b3bba7c1..fda8b45e21 100644 --- a/src/nvim/textobject.c +++ b/src/nvim/textobject.c @@ -19,6 +19,7 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/move.h" +#include "nvim/normal.h" #include "nvim/option_vars.h" #include "nvim/pos_defs.h" #include "nvim/search.h" @@ -427,6 +428,13 @@ int end_word(int count, bool bigword, bool stop, bool empty) curwin->w_cursor.coladd = 0; cls_bigword = bigword; + + // If adjusted cursor position previously, unadjust it. + if (*p_sel == 'e' && VIsual_active && VIsual_mode == 'v' + && VIsual_select_exclu_adj) { + unadjust_for_sel(); + } + while (--count >= 0) { // When inside a range of folded lines, move to the last char of the // last line. diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 31f95a1006..7c93be844a 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1,4 +1,4 @@ -// Terminal UI functions. Invoked (by ui_client.c) on the UI process. +// Terminal UI functions. Invoked by the UI process (ui_client.c), not the server. #include <assert.h> #include <inttypes.h> diff --git a/test/benchmark/keycodes_spec.lua b/test/benchmark/keycodes_spec.lua new file mode 100644 index 0000000000..443df2a6fb --- /dev/null +++ b/test/benchmark/keycodes_spec.lua @@ -0,0 +1,84 @@ +local n = require('test.functional.testnvim')() +local clear = n.clear +local api = n.api +local fn = n.fn + +local keycodes = require('src.nvim.keycodes') + +describe('nvim_replace_termcodes performance', function() + it('200 calls with a key repeated 5000 times', function() + clear() + local stats = {} + local sum = 0 + local ms = 1 / 1000000 + + for _, keycode in ipairs(keycodes.names) do + local notation = ('<%s>'):format(keycode[2]) + local str = notation:rep(5000) + + local start = vim.uv.hrtime() + for _ = 1, 200 do + api.nvim_replace_termcodes(str, false, true, true) + end + local elapsed = vim.uv.hrtime() - start + + table.insert(stats, elapsed) + sum = sum + elapsed + io.stdout:write(('\n%-20s%14.6f ms'):format(notation, elapsed * ms)) + io.stdout:flush() + end + io.stdout:write('\n') + + table.sort(stats) + print(('%18s'):rep(6):format('avg', 'min', '25%', 'median', '75%', 'max')) + print( + (' %14.6f ms'):rep(6):format( + sum / #stats * ms, + stats[1] * ms, + stats[1 + math.floor(#stats * 0.25)] * ms, + stats[1 + math.floor(#stats * 0.5)] * ms, + stats[1 + math.floor(#stats * 0.75)] * ms, + stats[#stats] * ms + ) + ) + end) +end) + +describe('keytrans() performance', function() + it('200 calls with a key repeated 5000 times', function() + clear() + local stats = {} + local sum = 0 + local ms = 1 / 1000000 + + for _, keycode in ipairs(keycodes.names) do + local notation = ('<%s>'):format(keycode[2]) + local str = api.nvim_replace_termcodes(notation, false, true, true):rep(5000) + + local start = vim.uv.hrtime() + for _ = 1, 200 do + fn.keytrans(str) + end + local elapsed = vim.uv.hrtime() - start + + table.insert(stats, elapsed) + sum = sum + elapsed + io.stdout:write(('\n%-20s%14.6f ms'):format(notation, elapsed * ms)) + io.stdout:flush() + end + io.stdout:write('\n') + + table.sort(stats) + print((' %17s'):rep(6):format('avg', 'min', '25%', 'median', '75%', 'max')) + print( + (' %14.6f ms'):rep(6):format( + sum / #stats * ms, + stats[1] * ms, + stats[1 + math.floor(#stats * 0.25)] * ms, + stats[1 + math.floor(#stats * 0.5)] * ms, + stats[1 + math.floor(#stats * 0.75)] * ms, + stats[#stats] * ms + ) + ) + end) +end) diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 6f53076f1f..31bb9841dc 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -83,7 +83,7 @@ end describe('API/extmarks', function() local screen local marks, positions, init_text, row, col - local ns, ns2 + local ns, ns2 ---@type integer, integer before_each(function() -- Initialize some namespaces and insert 12345 into a buffer @@ -1425,14 +1425,12 @@ describe('API/extmarks', function() end) it('throws consistent error codes', function() - local ns_invalid = ns2 + 1 - eq( - "Invalid 'ns_id': 3", - pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2]) - ) - eq("Invalid 'ns_id': 3", pcall_err(api.nvim_buf_del_extmark, 0, ns_invalid, marks[1])) - eq("Invalid 'ns_id': 3", pcall_err(get_extmarks, ns_invalid, positions[1], positions[2])) - eq("Invalid 'ns_id': 3", pcall_err(get_extmark_by_id, ns_invalid, marks[1])) + local ns_invalid = ns2 + 1 ---@type integer + local err = string.format("Invalid 'ns_id': %d", ns_invalid) + eq(err, pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2])) + eq(err, pcall_err(api.nvim_buf_del_extmark, 0, ns_invalid, marks[1])) + eq(err, pcall_err(get_extmarks, ns_invalid, positions[1], positions[2])) + eq(err, pcall_err(get_extmark_by_id, ns_invalid, marks[1])) end) it('when col = line-length, set the mark on eol', function() @@ -1535,7 +1533,7 @@ describe('API/extmarks', function() 0, 0, { - ns_id = 1, + ns_id = ns, end_col = 0, end_row = 1, right_gravity = true, @@ -1595,7 +1593,7 @@ describe('API/extmarks', function() hl_group = 'String', hl_mode = 'blend', line_hl_group = 'Statement', - ns_id = 1, + ns_id = ns, number_hl_group = 'Statement', priority = 0, right_gravity = false, @@ -1626,7 +1624,7 @@ describe('API/extmarks', function() 0, 0, { - ns_id = 1, + ns_id = ns, right_gravity = true, priority = 0, virt_text = { { '', 'Macro' }, { '', { 'Type', 'Search' } }, { '' } }, @@ -1647,7 +1645,7 @@ describe('API/extmarks', function() 0, 0, { - ns_id = 1, + ns_id = ns, right_gravity = true, ui_watched = true, priority = 0, @@ -1663,7 +1661,7 @@ describe('API/extmarks', function() 0, 0, { - ns_id = 1, + ns_id = ns, cursorline_hl_group = 'Statement', priority = 4096, right_gravity = true, @@ -1683,7 +1681,7 @@ describe('API/extmarks', function() end_col = 1, end_right_gravity = false, end_row = 0, - ns_id = 1, + ns_id = ns, right_gravity = true, spell = true, }, @@ -1700,7 +1698,7 @@ describe('API/extmarks', function() end_col = 1, end_right_gravity = false, end_row = 0, - ns_id = 1, + ns_id = ns, right_gravity = true, spell = false, }, diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index b5fcd88f71..18e6dbd9b6 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -3167,14 +3167,23 @@ describe('API', function() describe('nvim_create_namespace', function() it('works', function() - eq({}, api.nvim_get_namespaces()) - eq(1, api.nvim_create_namespace('ns-1')) - eq(2, api.nvim_create_namespace('ns-2')) - eq(1, api.nvim_create_namespace('ns-1')) - eq({ ['ns-1'] = 1, ['ns-2'] = 2 }, api.nvim_get_namespaces()) - eq(3, api.nvim_create_namespace('')) - eq(4, api.nvim_create_namespace('')) - eq({ ['ns-1'] = 1, ['ns-2'] = 2 }, api.nvim_get_namespaces()) + local orig = api.nvim_get_namespaces() + local base = vim.iter(orig):fold(0, function(acc, _, v) + return math.max(acc, v) + end) + eq(base + 1, api.nvim_create_namespace('ns-1')) + eq(base + 2, api.nvim_create_namespace('ns-2')) + eq(base + 1, api.nvim_create_namespace('ns-1')) + + local expected = vim.tbl_extend('error', orig, { + ['ns-1'] = base + 1, + ['ns-2'] = base + 2, + }) + + eq(expected, api.nvim_get_namespaces()) + eq(base + 3, api.nvim_create_namespace('')) + eq(base + 4, api.nvim_create_namespace('')) + eq(expected, api.nvim_get_namespaces()) end) end) diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua index 7b10eb05ef..11617905dd 100644 --- a/test/functional/core/channels_spec.lua +++ b/test/functional/core/channels_spec.lua @@ -3,6 +3,8 @@ local n = require('test.functional.testnvim')() local clear, eq, eval, next_msg, ok, source = n.clear, t.eq, n.eval, n.next_msg, t.ok, n.source local command, fn, api = n.command, n.fn, n.api +local feed = n.feed +local exec_lua = n.exec_lua local matches = t.matches local sleep = vim.uv.sleep local get_session, set_session = n.get_session, n.set_session @@ -416,6 +418,53 @@ describe('channels', function() -- works correctly with no output eq({ 'notification', 'exit', { id, 1, { '' } } }, next_msg()) end) + + it('ChanOpen works with vim.wait() from another autocommand #32706', function() + exec_lua([[ + vim.api.nvim_create_autocmd('ChanOpen', { + callback = function(ev) + _G.chan = vim.v.event.info.id + end, + }) + vim.api.nvim_create_autocmd('InsertEnter', { + buffer = 0, + callback = function() + local chan = vim.fn.jobstart({ 'cat' }) + _G.result = vim.wait(3000, function() + return _G.chan == chan + end) + end, + }) + ]]) + feed('i') + retry(nil, 4000, function() + eq(true, exec_lua('return _G.result')) + end) + end) + + it('ChanInfo works with vim.wait() from another autocommand #32706', function() + exec_lua([[ + vim.api.nvim_create_autocmd('ChanInfo', { + callback = function(ev) + _G.foo = vim.v.event.info.client.attributes.foo + end, + }) + vim.api.nvim_create_autocmd('InsertEnter', { + buffer = 0, + callback = function() + local chan = vim.fn.jobstart({ 'cat' }) + _G.result = vim.wait(3000, function() + return _G.foo == 'bar' + end) + end, + }) + ]]) + feed('i') + api.nvim_set_client_info('test', {}, 'remote', {}, { foo = 'bar' }) + retry(nil, 4000, function() + eq(true, exec_lua('return _G.result')) + end) + end) end) describe('loopback', function() diff --git a/test/functional/editor/defaults_spec.lua b/test/functional/editor/defaults_spec.lua index 876810ce6f..9843238e35 100644 --- a/test/functional/editor/defaults_spec.lua +++ b/test/functional/editor/defaults_spec.lua @@ -10,7 +10,7 @@ local Screen = require('test.functional.ui.screen') describe('default', function() describe('autocommands', function() - it('nvim_terminal.TermClose closes terminal with default shell on success', function() + it('nvim.terminal.TermClose closes terminal with default shell on success', function() n.clear() n.api.nvim_set_option_value('shell', n.testprg('shell-test'), {}) n.command('set shellcmdflag=EXIT shellredir= shellpipe= shellquote= shellxquote=') diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index 5d7ab2ad12..6798d53d2b 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -789,15 +789,19 @@ function tests.code_action_with_resolve() end, body = function() notify('start') - local cmd = { - title = 'Command 1', - command = 'dummy1', - } + local cmd = { title = 'Action 1' } expect_request('textDocument/codeAction', function() return nil, { cmd } end) expect_request('codeAction/resolve', function() - return nil, cmd + return nil, + { + title = 'Action 1', + command = { + title = 'Command 1', + command = 'dummy1', + }, + } end) notify('shutdown') end, diff --git a/test/functional/legacy/breakindent_spec.lua b/test/functional/legacy/breakindent_spec.lua index db27e19e50..42d28b1231 100644 --- a/test/functional/legacy/breakindent_spec.lua +++ b/test/functional/legacy/breakindent_spec.lua @@ -12,11 +12,6 @@ describe('breakindent', function() -- oldtest: Test_cursor_position_with_showbreak() it('cursor shown at correct position with showbreak', function() local screen = Screen.new(75, 6) - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, -- NonText - [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, -- SignColumn - [2] = { bold = true }, -- ModeMsg - }) exec([[ set listchars=eol:$ let &signcolumn = 'yes' @@ -29,10 +24,10 @@ describe('breakindent', function() feed('AX') screen:expect([[ - {1: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxX| - {1: }^second line | - {0:~ }|*3 - {2:-- INSERT --} | + {7: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxX| + {7: }^second line | + {1:~ }|*3 + {5:-- INSERT --} | ]]) -- No line wraps, so changing 'showbreak' should lead to the same screen. command('setlocal showbreak=+') @@ -43,19 +38,19 @@ describe('breakindent', function() -- The first line now wraps because of "eol" in 'listchars'. command('setlocal list') screen:expect([[ - {1: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxX| - {1: } {0:+^$} | - {1: }second line{0:$} | - {0:~ }|*2 - {2:-- INSERT --} | + {7: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxX| + {7: } {1:+^$} | + {7: }second line{1:$} | + {1:~ }|*2 + {5:-- INSERT --} | ]]) command('setlocal nobreakindent') screen:expect([[ - {1: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxX| - {1: }{0:+^$} | - {1: }second line{0:$} | - {0:~ }|*2 - {2:-- INSERT --} | + {7: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxX| + {7: }{1:+^$} | + {7: }second line{1:$} | + {1:~ }|*2 + {5:-- INSERT --} | ]]) end) diff --git a/test/functional/legacy/cmdline_spec.lua b/test/functional/legacy/cmdline_spec.lua index 819b40323a..62e77e9b85 100644 --- a/test/functional/legacy/cmdline_spec.lua +++ b/test/functional/legacy/cmdline_spec.lua @@ -294,41 +294,34 @@ describe('cmdwin', function() -- oldtest: Test_cmdwin_interrupted() it('still uses a new buffer when interrupting more prompt on open', function() local screen = Screen.new(30, 16) - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, -- NonText - [1] = { bold = true, reverse = true }, -- StatusLine - [2] = { reverse = true }, -- StatusLineNC - [3] = { bold = true, foreground = Screen.colors.SeaGreen }, -- MoreMsg - [4] = { bold = true }, -- ModeMsg - }) command('set more') command('autocmd WinNew * highlight') feed('q:') - screen:expect({ any = pesc('{3:-- More --}^') }) + screen:expect({ any = pesc('{6:-- More --}^') }) feed('q') screen:expect([[ | - {0:~ }|*5 + {1:~ }|*5 {2:[No Name] }| - {0::}^ | - {0:~ }|*6 - {1:[Command Line] }| + {1::}^ | + {1:~ }|*6 + {3:[Command Line] }| | ]]) feed([[aecho 'done']]) screen:expect([[ | - {0:~ }|*5 + {1:~ }|*5 {2:[No Name] }| - {0::}echo 'done'^ | - {0:~ }|*6 - {1:[Command Line] }| - {4:-- INSERT --} | + {1::}echo 'done'^ | + {1:~ }|*6 + {3:[Command Line] }| + {5:-- INSERT --} | ]]) feed('<CR>') screen:expect([[ ^ | - {0:~ }|*14 + {1:~ }|*14 done | ]]) end) diff --git a/test/functional/legacy/display_spec.lua b/test/functional/legacy/display_spec.lua index 945cd21710..c93b2d96d8 100644 --- a/test/functional/legacy/display_spec.lua +++ b/test/functional/legacy/display_spec.lua @@ -45,11 +45,6 @@ describe('display', function() local function run_test_display_lastline(euro) local screen = Screen.new(75, 10) - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue }, -- NonText - [2] = { bold = true, reverse = true }, -- StatusLine - [3] = { reverse = true }, -- StatusLineNC - }) exec([[ call setline(1, ['aaa', 'b'->repeat(200)]) set display=truncate @@ -68,7 +63,7 @@ describe('display', function() b│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | b│{1:~ }|*3 {1:@}│{1:~ }| - {2:< }{3:[No Name] [+] }| + {3:< }{2:[No Name] [+] }| | ]]):gsub('@', fillchar))) @@ -82,7 +77,7 @@ describe('display', function() bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │b| {1:~ }│b|*3 {1:~ }│{1:@}| - {2:[No Name] [+] }{3:<}| + {3:[No Name] [+] }{2:<}| | ]]):gsub('@', fillchar))) @@ -95,11 +90,11 @@ describe('display', function() ^aaa | bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| {1:@@@ }| - {2:[No Name] [+] }| + {3:[No Name] [+] }| aaa | bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|*2 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | - {3:[No Name] [+] }| + {2:[No Name] [+] }| | ]]):gsub('@', fillchar))) @@ -112,7 +107,7 @@ describe('display', function() bb│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | bb│{1:~ }|*3 {1:@@}│{1:~ }| - {2:< }{3:[No Name] [+] }| + {3:< }{2:[No Name] [+] }| | ]]):gsub('@', fillchar))) end diff --git a/test/functional/legacy/matchparen_spec.lua b/test/functional/legacy/matchparen_spec.lua index d992420c30..367830b564 100644 --- a/test/functional/legacy/matchparen_spec.lua +++ b/test/functional/legacy/matchparen_spec.lua @@ -195,4 +195,48 @@ describe('matchparen', function() {5:-- INSERT --} | ]]) end) + + -- oldtest: Test_matchparen_ignore_sh_case() + it('ignores shell case statements', function() + local screen = Screen.new(40, 15) + exec([[ + syntax on + source $VIMRUNTIME/plugin/matchparen.vim + set ft=sh + call setline(1, [ + \ '#!/bin/sh', + \ 'SUSUWU_PRINT() (', + \ ' case "${LEVEL}" in', + \ ' "$SUSUWU_SH_NOTICE")', + \ ' ${SUSUWU_S} && return 1', + \ ' ;;', + \ ' "$SUSUWU_SH_DEBUG")', + \ ' (! ${SUSUWU_VERBOSE}) && return 1', + \ ' ;;', + \ ' esac', + \ ' # snip', + \ ')' + \ ]) + call cursor(4, 26) + ]]) + screen:add_extra_attr_ids({ + [100] = { foreground = tonumber('0x6a0dad') }, + }) + screen:expect([[ + {18:#!/bin/sh} | + {25:SUSUWU_PRINT() (} | + {15:case} {15:"}{100:${LEVEL}}{15:"} {15:in} | + {15:"}{100:$SUSUWU_SH_NOTICE}{15:"^)} | + {100:${SUSUWU_S}} {15:&&} {15:return} {26:1} | + {15:;;} | + {15:"}{100:$SUSUWU_SH_DEBUG}{15:")} | + {100:(}{15:!} {100:${SUSUWU_VERBOSE})} {15:&&} {15:return} {26:1} | + {15:;;} | + {15:esac} | + {18:# snip} | + {25:)} | + {1:~ }|*2 + | + ]]) + end) end) diff --git a/test/functional/legacy/messages_spec.lua b/test/functional/legacy/messages_spec.lua index f7691fdacc..e73a0780aa 100644 --- a/test/functional/legacy/messages_spec.lua +++ b/test/functional/legacy/messages_spec.lua @@ -122,10 +122,6 @@ describe('messages', function() -- oldtest: Test_message_more() it('works', function() screen = Screen.new(75, 6) - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.SeaGreen }, -- MoreMsg - [2] = { foreground = Screen.colors.Brown }, -- LineNr - }) command('call setline(1, range(1, 100))') @@ -140,194 +136,194 @@ describe('messages', function() ]]) feed('\n') screen:expect([[ - {2: 1 }1 | - {2: 2 }2 | - {2: 3 }3 | - {2: 4 }4 | - {2: 5 }5 | - {1:-- More --}^ | + {8: 1 }1 | + {8: 2 }2 | + {8: 3 }3 | + {8: 4 }4 | + {8: 5 }5 | + {6:-- More --}^ | ]]) feed('?') screen:expect([[ - {2: 1 }1 | - {2: 2 }2 | - {2: 3 }3 | - {2: 4 }4 | - {2: 5 }5 | - {1:-- More -- SPACE/d/j: screen/page/line down, b/u/k: up, q: quit }^ | + {8: 1 }1 | + {8: 2 }2 | + {8: 3 }3 | + {8: 4 }4 | + {8: 5 }5 | + {6:-- More -- SPACE/d/j: screen/page/line down, b/u/k: up, q: quit }^ | ]]) -- Down a line with j, <CR>, <NL> or <Down>. feed('j') screen:expect([[ - {2: 2 }2 | - {2: 3 }3 | - {2: 4 }4 | - {2: 5 }5 | - {2: 6 }6 | - {1:-- More --}^ | + {8: 2 }2 | + {8: 3 }3 | + {8: 4 }4 | + {8: 5 }5 | + {8: 6 }6 | + {6:-- More --}^ | ]]) feed('<NL>') screen:expect([[ - {2: 3 }3 | - {2: 4 }4 | - {2: 5 }5 | - {2: 6 }6 | - {2: 7 }7 | - {1:-- More --}^ | + {8: 3 }3 | + {8: 4 }4 | + {8: 5 }5 | + {8: 6 }6 | + {8: 7 }7 | + {6:-- More --}^ | ]]) feed('<CR>') screen:expect([[ - {2: 4 }4 | - {2: 5 }5 | - {2: 6 }6 | - {2: 7 }7 | - {2: 8 }8 | - {1:-- More --}^ | + {8: 4 }4 | + {8: 5 }5 | + {8: 6 }6 | + {8: 7 }7 | + {8: 8 }8 | + {6:-- More --}^ | ]]) feed('<Down>') screen:expect([[ - {2: 5 }5 | - {2: 6 }6 | - {2: 7 }7 | - {2: 8 }8 | - {2: 9 }9 | - {1:-- More --}^ | + {8: 5 }5 | + {8: 6 }6 | + {8: 7 }7 | + {8: 8 }8 | + {8: 9 }9 | + {6:-- More --}^ | ]]) -- Down a screen with <Space>, f, or <PageDown>. feed('f') screen:expect([[ - {2: 10 }10 | - {2: 11 }11 | - {2: 12 }12 | - {2: 13 }13 | - {2: 14 }14 | - {1:-- More --}^ | + {8: 10 }10 | + {8: 11 }11 | + {8: 12 }12 | + {8: 13 }13 | + {8: 14 }14 | + {6:-- More --}^ | ]]) feed('<Space>') screen:expect([[ - {2: 15 }15 | - {2: 16 }16 | - {2: 17 }17 | - {2: 18 }18 | - {2: 19 }19 | - {1:-- More --}^ | + {8: 15 }15 | + {8: 16 }16 | + {8: 17 }17 | + {8: 18 }18 | + {8: 19 }19 | + {6:-- More --}^ | ]]) feed('<PageDown>') screen:expect([[ - {2: 20 }20 | - {2: 21 }21 | - {2: 22 }22 | - {2: 23 }23 | - {2: 24 }24 | - {1:-- More --}^ | + {8: 20 }20 | + {8: 21 }21 | + {8: 22 }22 | + {8: 23 }23 | + {8: 24 }24 | + {6:-- More --}^ | ]]) -- Down a page (half a screen) with d. feed('d') screen:expect([[ - {2: 23 }23 | - {2: 24 }24 | - {2: 25 }25 | - {2: 26 }26 | - {2: 27 }27 | - {1:-- More --}^ | + {8: 23 }23 | + {8: 24 }24 | + {8: 25 }25 | + {8: 26 }26 | + {8: 27 }27 | + {6:-- More --}^ | ]]) -- Down all the way with 'G'. feed('G') screen:expect([[ - {2: 96 }96 | - {2: 97 }97 | - {2: 98 }98 | - {2: 99 }99 | - {2:100 }100 | - {1:Press ENTER or type command to continue}^ | + {8: 96 }96 | + {8: 97 }97 | + {8: 98 }98 | + {8: 99 }99 | + {8:100 }100 | + {6:Press ENTER or type command to continue}^ | ]]) -- Up a line k, <BS> or <Up>. feed('k') screen:expect([[ - {2: 95 }95 | - {2: 96 }96 | - {2: 97 }97 | - {2: 98 }98 | - {2: 99 }99 | - {1:-- More --}^ | + {8: 95 }95 | + {8: 96 }96 | + {8: 97 }97 | + {8: 98 }98 | + {8: 99 }99 | + {6:-- More --}^ | ]]) feed('<BS>') screen:expect([[ - {2: 94 }94 | - {2: 95 }95 | - {2: 96 }96 | - {2: 97 }97 | - {2: 98 }98 | - {1:-- More --}^ | + {8: 94 }94 | + {8: 95 }95 | + {8: 96 }96 | + {8: 97 }97 | + {8: 98 }98 | + {6:-- More --}^ | ]]) feed('<Up>') screen:expect([[ - {2: 93 }93 | - {2: 94 }94 | - {2: 95 }95 | - {2: 96 }96 | - {2: 97 }97 | - {1:-- More --}^ | + {8: 93 }93 | + {8: 94 }94 | + {8: 95 }95 | + {8: 96 }96 | + {8: 97 }97 | + {6:-- More --}^ | ]]) -- Up a screen with b or <PageUp>. feed('b') screen:expect([[ - {2: 88 }88 | - {2: 89 }89 | - {2: 90 }90 | - {2: 91 }91 | - {2: 92 }92 | - {1:-- More --}^ | + {8: 88 }88 | + {8: 89 }89 | + {8: 90 }90 | + {8: 91 }91 | + {8: 92 }92 | + {6:-- More --}^ | ]]) feed('<PageUp>') screen:expect([[ - {2: 83 }83 | - {2: 84 }84 | - {2: 85 }85 | - {2: 86 }86 | - {2: 87 }87 | - {1:-- More --}^ | + {8: 83 }83 | + {8: 84 }84 | + {8: 85 }85 | + {8: 86 }86 | + {8: 87 }87 | + {6:-- More --}^ | ]]) -- Up a page (half a screen) with u. feed('u') screen:expect([[ - {2: 80 }80 | - {2: 81 }81 | - {2: 82 }82 | - {2: 83 }83 | - {2: 84 }84 | - {1:-- More --}^ | + {8: 80 }80 | + {8: 81 }81 | + {8: 82 }82 | + {8: 83 }83 | + {8: 84 }84 | + {6:-- More --}^ | ]]) -- Up all the way with 'g'. feed('g') screen:expect([[ :%p# | - {2: 1 }1 | - {2: 2 }2 | - {2: 3 }3 | - {2: 4 }4 | - {1:-- More --}^ | + {8: 1 }1 | + {8: 2 }2 | + {8: 3 }3 | + {8: 4 }4 | + {6:-- More --}^ | ]]) -- All the way down. Pressing f should do nothing but pressing -- space should end the more prompt. feed('G') screen:expect([[ - {2: 96 }96 | - {2: 97 }97 | - {2: 98 }98 | - {2: 99 }99 | - {2:100 }100 | - {1:Press ENTER or type command to continue}^ | + {8: 96 }96 | + {8: 97 }97 | + {8: 98 }98 | + {8: 99 }99 | + {8:100 }100 | + {6:Press ENTER or type command to continue}^ | ]]) feed('f') screen:expect_unchanged() @@ -344,34 +340,34 @@ describe('messages', function() -- Pressing g< shows the previous command output. feed('g<lt>') screen:expect([[ - {2: 96 }96 | - {2: 97 }97 | - {2: 98 }98 | - {2: 99 }99 | - {2:100 }100 | - {1:Press ENTER or type command to continue}^ | + {8: 96 }96 | + {8: 97 }97 | + {8: 98 }98 | + {8: 99 }99 | + {8:100 }100 | + {6:Press ENTER or type command to continue}^ | ]]) -- A command line that doesn't print text is appended to scrollback, -- even if it invokes a nested command line. feed([[:<C-R>=':'<CR>:<CR>g<lt>]]) screen:expect([[ - {2: 97 }97 | - {2: 98 }98 | - {2: 99 }99 | - {2:100 }100 | + {8: 97 }97 | + {8: 98 }98 | + {8: 99 }99 | + {8:100 }100 | ::: | - {1:Press ENTER or type command to continue}^ | + {6:Press ENTER or type command to continue}^ | ]]) feed(':%p#\n') screen:expect([[ - {2: 1 }1 | - {2: 2 }2 | - {2: 3 }3 | - {2: 4 }4 | - {2: 5 }5 | - {1:-- More --}^ | + {8: 1 }1 | + {8: 2 }2 | + {8: 3 }3 | + {8: 4 }4 | + {8: 5 }5 | + {6:-- More --}^ | ]]) -- Stop command output with q, <Esc> or CTRL-C. @@ -388,30 +384,30 @@ describe('messages', function() -- Execute a : command from the more prompt feed(':%p#\n') screen:expect([[ - {2: 1 }1 | - {2: 2 }2 | - {2: 3 }3 | - {2: 4 }4 | - {2: 5 }5 | - {1:-- More --}^ | + {8: 1 }1 | + {8: 2 }2 | + {8: 3 }3 | + {8: 4 }4 | + {8: 5 }5 | + {6:-- More --}^ | ]]) feed(':') screen:expect([[ - {2: 1 }1 | - {2: 2 }2 | - {2: 3 }3 | - {2: 4 }4 | - {2: 5 }5 | + {8: 1 }1 | + {8: 2 }2 | + {8: 3 }3 | + {8: 4 }4 | + {8: 5 }5 | :^ | ]]) feed("echo 'Hello'\n") screen:expect([[ - {2: 2 }2 | - {2: 3 }3 | - {2: 4 }4 | - {2: 5 }5 | + {8: 2 }2 | + {8: 3 }3 | + {8: 4 }4 | + {8: 5 }5 | Hello | - {1:Press ENTER or type command to continue}^ | + {6:Press ENTER or type command to continue}^ | ]]) end) diff --git a/test/functional/legacy/number_spec.lua b/test/functional/legacy/number_spec.lua index 3212524f9d..58a08b9a13 100644 --- a/test/functional/legacy/number_spec.lua +++ b/test/functional/legacy/number_spec.lua @@ -12,11 +12,9 @@ describe("'number' and 'relativenumber'", function() -- oldtest: Test_relativenumber_colors() it('LineNr, LineNrAbove and LineNrBelow', function() local screen = Screen.new(50, 10) - screen:set_default_attr_ids({ - [1] = { foreground = Screen.colors.Red }, - [2] = { foreground = Screen.colors.Blue }, - [3] = { foreground = Screen.colors.Green }, - }) + screen:add_extra_attr_ids { + [100] = { foreground = Screen.colors.WebGreen }, + } exec([[ call setline(1, range(200)) 111 @@ -24,54 +22,54 @@ describe("'number' and 'relativenumber'", function() hi LineNr guifg=red ]]) screen:expect([[ - {1: 4 }106 | - {1: 3 }107 | - {1: 2 }108 | - {1: 1 }109 | - {1:111 }^110 | - {1: 1 }111 | - {1: 2 }112 | - {1: 3 }113 | - {1: 4 }114 | + {19: 4 }106 | + {19: 3 }107 | + {19: 2 }108 | + {19: 1 }109 | + {19:111 }^110 | + {19: 1 }111 | + {19: 2 }112 | + {19: 3 }113 | + {19: 4 }114 | | ]]) command('hi LineNrAbove guifg=blue') screen:expect([[ - {2: 4 }106 | - {2: 3 }107 | - {2: 2 }108 | - {2: 1 }109 | - {1:111 }^110 | - {1: 1 }111 | - {1: 2 }112 | - {1: 3 }113 | - {1: 4 }114 | + {18: 4 }106 | + {18: 3 }107 | + {18: 2 }108 | + {18: 1 }109 | + {19:111 }^110 | + {19: 1 }111 | + {19: 2 }112 | + {19: 3 }113 | + {19: 4 }114 | | ]]) command('hi LineNrBelow guifg=green') screen:expect([[ - {2: 4 }106 | - {2: 3 }107 | - {2: 2 }108 | - {2: 1 }109 | - {1:111 }^110 | - {3: 1 }111 | - {3: 2 }112 | - {3: 3 }113 | - {3: 4 }114 | + {18: 4 }106 | + {18: 3 }107 | + {18: 2 }108 | + {18: 1 }109 | + {19:111 }^110 | + {100: 1 }111 | + {100: 2 }112 | + {100: 3 }113 | + {100: 4 }114 | | ]]) command('hi clear LineNrAbove') screen:expect([[ - {1: 4 }106 | - {1: 3 }107 | - {1: 2 }108 | - {1: 1 }109 | - {1:111 }^110 | - {3: 1 }111 | - {3: 2 }112 | - {3: 3 }113 | - {3: 4 }114 | + {19: 4 }106 | + {19: 3 }107 | + {19: 2 }108 | + {19: 1 }109 | + {19:111 }^110 | + {100: 1 }111 | + {100: 2 }112 | + {100: 3 }113 | + {100: 4 }114 | | ]]) end) @@ -79,12 +77,11 @@ describe("'number' and 'relativenumber'", function() -- oldtest: Test_relativenumber_colors_wrapped() it('LineNr, LineNrAbove and LineNrBelow with wrapped lines', function() local screen = Screen.new(50, 20) - screen:set_default_attr_ids({ - [1] = { background = Screen.colors.Red, foreground = Screen.colors.Black }, - [2] = { background = Screen.colors.Blue, foreground = Screen.colors.Black }, - [3] = { background = Screen.colors.Green, foreground = Screen.colors.Black }, - [4] = { bold = true, foreground = Screen.colors.Blue }, - }) + screen:add_extra_attr_ids { + [100] = { foreground = Screen.colors.Gray0, background = Screen.colors.Red }, + [101] = { foreground = Screen.colors.Gray0, background = Screen.colors.Blue1 }, + [102] = { foreground = Screen.colors.Gray0, background = Screen.colors.WebGreen }, + } exec([[ set display=lastline scrolloff=0 call setline(1, range(200)->map('v:val->string()->repeat(40)')) @@ -95,117 +92,117 @@ describe("'number' and 'relativenumber'", function() hi LineNrBelow guibg=green guifg=black ]]) screen:expect([[ - {2: 2 }1081081081081081081081081081081081081081081081| - {2: }0810810810810810810810810810810810810810810810| - {2: }8108108108108108108108108108 | - {2: 1 }1091091091091091091091091091091091091091091091| - {2: }0910910910910910910910910910910910910910910910| - {2: }9109109109109109109109109109 | - {1:111 }^1101101101101101101101101101101101101101101101| - {1: }1011011011011011011011011011011011011011011011| - {1: }0110110110110110110110110110 | - {3: 1 }1111111111111111111111111111111111111111111111| - {3: }1111111111111111111111111111111111111111111111| - {3: }1111111111111111111111111111 | - {3: 2 }1121121121121121121121121121121121121121121121| - {3: }1211211211211211211211211211211211211211211211| - {3: }2112112112112112112112112112 | - {3: 3 }1131131131131131131131131131131131131131131131| - {3: }1311311311311311311311311311311311311311311311| - {3: }3113113113113113113113113113 | - {3: 4 }1141141141141141141141141141141141141141141{4:@@@}| + {101: 2 }1081081081081081081081081081081081081081081081| + {101: }0810810810810810810810810810810810810810810810| + {101: }8108108108108108108108108108 | + {101: 1 }1091091091091091091091091091091091091091091091| + {101: }0910910910910910910910910910910910910910910910| + {101: }9109109109109109109109109109 | + {100:111 }^1101101101101101101101101101101101101101101101| + {100: }1011011011011011011011011011011011011011011011| + {100: }0110110110110110110110110110 | + {102: 1 }1111111111111111111111111111111111111111111111| + {102: }1111111111111111111111111111111111111111111111| + {102: }1111111111111111111111111111 | + {102: 2 }1121121121121121121121121121121121121121121121| + {102: }1211211211211211211211211211211211211211211211| + {102: }2112112112112112112112112112 | + {102: 3 }1131131131131131131131131131131131131131131131| + {102: }1311311311311311311311311311311311311311311311| + {102: }3113113113113113113113113113 | + {102: 4 }1141141141141141141141141141141141141141141{1:@@@}| | ]]) feed('k') screen:expect([[ - {2: 1 }1081081081081081081081081081081081081081081081| - {2: }0810810810810810810810810810810810810810810810| - {2: }8108108108108108108108108108 | - {1:110 }^1091091091091091091091091091091091091091091091| - {1: }0910910910910910910910910910910910910910910910| - {1: }9109109109109109109109109109 | - {3: 1 }1101101101101101101101101101101101101101101101| - {3: }1011011011011011011011011011011011011011011011| - {3: }0110110110110110110110110110 | - {3: 2 }1111111111111111111111111111111111111111111111| - {3: }1111111111111111111111111111111111111111111111| - {3: }1111111111111111111111111111 | - {3: 3 }1121121121121121121121121121121121121121121121| - {3: }1211211211211211211211211211211211211211211211| - {3: }2112112112112112112112112112 | - {3: 4 }1131131131131131131131131131131131131131131131| - {3: }1311311311311311311311311311311311311311311311| - {3: }3113113113113113113113113113 | - {3: 5 }1141141141141141141141141141141141141141141{4:@@@}| + {101: 1 }1081081081081081081081081081081081081081081081| + {101: }0810810810810810810810810810810810810810810810| + {101: }8108108108108108108108108108 | + {100:110 }^1091091091091091091091091091091091091091091091| + {100: }0910910910910910910910910910910910910910910910| + {100: }9109109109109109109109109109 | + {102: 1 }1101101101101101101101101101101101101101101101| + {102: }1011011011011011011011011011011011011011011011| + {102: }0110110110110110110110110110 | + {102: 2 }1111111111111111111111111111111111111111111111| + {102: }1111111111111111111111111111111111111111111111| + {102: }1111111111111111111111111111 | + {102: 3 }1121121121121121121121121121121121121121121121| + {102: }1211211211211211211211211211211211211211211211| + {102: }2112112112112112112112112112 | + {102: 4 }1131131131131131131131131131131131131131131131| + {102: }1311311311311311311311311311311311311311311311| + {102: }3113113113113113113113113113 | + {102: 5 }1141141141141141141141141141141141141141141{1:@@@}| | ]]) feed('2j') screen:expect([[ - {2: 3 }1081081081081081081081081081081081081081081081| - {2: }0810810810810810810810810810810810810810810810| - {2: }8108108108108108108108108108 | - {2: 2 }1091091091091091091091091091091091091091091091| - {2: }0910910910910910910910910910910910910910910910| - {2: }9109109109109109109109109109 | - {2: 1 }1101101101101101101101101101101101101101101101| - {2: }1011011011011011011011011011011011011011011011| - {2: }0110110110110110110110110110 | - {1:112 }^1111111111111111111111111111111111111111111111| - {1: }1111111111111111111111111111111111111111111111| - {1: }1111111111111111111111111111 | - {3: 1 }1121121121121121121121121121121121121121121121| - {3: }1211211211211211211211211211211211211211211211| - {3: }2112112112112112112112112112 | - {3: 2 }1131131131131131131131131131131131131131131131| - {3: }1311311311311311311311311311311311311311311311| - {3: }3113113113113113113113113113 | - {3: 3 }1141141141141141141141141141141141141141141{4:@@@}| + {101: 3 }1081081081081081081081081081081081081081081081| + {101: }0810810810810810810810810810810810810810810810| + {101: }8108108108108108108108108108 | + {101: 2 }1091091091091091091091091091091091091091091091| + {101: }0910910910910910910910910910910910910910910910| + {101: }9109109109109109109109109109 | + {101: 1 }1101101101101101101101101101101101101101101101| + {101: }1011011011011011011011011011011011011011011011| + {101: }0110110110110110110110110110 | + {100:112 }^1111111111111111111111111111111111111111111111| + {100: }1111111111111111111111111111111111111111111111| + {100: }1111111111111111111111111111 | + {102: 1 }1121121121121121121121121121121121121121121121| + {102: }1211211211211211211211211211211211211211211211| + {102: }2112112112112112112112112112 | + {102: 2 }1131131131131131131131131131131131131131131131| + {102: }1311311311311311311311311311311311311311311311| + {102: }3113113113113113113113113113 | + {102: 3 }1141141141141141141141141141141141141141141{1:@@@}| | ]]) feed('2j') screen:expect([[ - {2: 5 }1081081081081081081081081081081081081081081081| - {2: }0810810810810810810810810810810810810810810810| - {2: }8108108108108108108108108108 | - {2: 4 }1091091091091091091091091091091091091091091091| - {2: }0910910910910910910910910910910910910910910910| - {2: }9109109109109109109109109109 | - {2: 3 }1101101101101101101101101101101101101101101101| - {2: }1011011011011011011011011011011011011011011011| - {2: }0110110110110110110110110110 | - {2: 2 }1111111111111111111111111111111111111111111111| - {2: }1111111111111111111111111111111111111111111111| - {2: }1111111111111111111111111111 | - {2: 1 }1121121121121121121121121121121121121121121121| - {2: }1211211211211211211211211211211211211211211211| - {2: }2112112112112112112112112112 | - {1:114 }^1131131131131131131131131131131131131131131131| - {1: }1311311311311311311311311311311311311311311311| - {1: }3113113113113113113113113113 | - {3: 1 }1141141141141141141141141141141141141141141{4:@@@}| + {101: 5 }1081081081081081081081081081081081081081081081| + {101: }0810810810810810810810810810810810810810810810| + {101: }8108108108108108108108108108 | + {101: 4 }1091091091091091091091091091091091091091091091| + {101: }0910910910910910910910910910910910910910910910| + {101: }9109109109109109109109109109 | + {101: 3 }1101101101101101101101101101101101101101101101| + {101: }1011011011011011011011011011011011011011011011| + {101: }0110110110110110110110110110 | + {101: 2 }1111111111111111111111111111111111111111111111| + {101: }1111111111111111111111111111111111111111111111| + {101: }1111111111111111111111111111 | + {101: 1 }1121121121121121121121121121121121121121121121| + {101: }1211211211211211211211211211211211211211211211| + {101: }2112112112112112112112112112 | + {100:114 }^1131131131131131131131131131131131131131131131| + {100: }1311311311311311311311311311311311311311311311| + {100: }3113113113113113113113113113 | + {102: 1 }1141141141141141141141141141141141141141141{1:@@@}| | ]]) feed('k') screen:expect([[ - {2: 4 }1081081081081081081081081081081081081081081081| - {2: }0810810810810810810810810810810810810810810810| - {2: }8108108108108108108108108108 | - {2: 3 }1091091091091091091091091091091091091091091091| - {2: }0910910910910910910910910910910910910910910910| - {2: }9109109109109109109109109109 | - {2: 2 }1101101101101101101101101101101101101101101101| - {2: }1011011011011011011011011011011011011011011011| - {2: }0110110110110110110110110110 | - {2: 1 }1111111111111111111111111111111111111111111111| - {2: }1111111111111111111111111111111111111111111111| - {2: }1111111111111111111111111111 | - {1:113 }^1121121121121121121121121121121121121121121121| - {1: }1211211211211211211211211211211211211211211211| - {1: }2112112112112112112112112112 | - {3: 1 }1131131131131131131131131131131131131131131131| - {3: }1311311311311311311311311311311311311311311311| - {3: }3113113113113113113113113113 | - {3: 2 }1141141141141141141141141141141141141141141{4:@@@}| + {101: 4 }1081081081081081081081081081081081081081081081| + {101: }0810810810810810810810810810810810810810810810| + {101: }8108108108108108108108108108 | + {101: 3 }1091091091091091091091091091091091091091091091| + {101: }0910910910910910910910910910910910910910910910| + {101: }9109109109109109109109109109 | + {101: 2 }1101101101101101101101101101101101101101101101| + {101: }1011011011011011011011011011011011011011011011| + {101: }0110110110110110110110110110 | + {101: 1 }1111111111111111111111111111111111111111111111| + {101: }1111111111111111111111111111111111111111111111| + {101: }1111111111111111111111111111 | + {100:113 }^1121121121121121121121121121121121121121121121| + {100: }1211211211211211211211211211211211211211211211| + {100: }2112112112112112112112112112 | + {102: 1 }1131131131131131131131131131131131131131131131| + {102: }1311311311311311311311311311311311311311311311| + {102: }3113113113113113113113113113 | + {102: 2 }1141141141141141141141141141141141141141141{1:@@@}| | ]]) end) diff --git a/test/functional/legacy/scroll_opt_spec.lua b/test/functional/legacy/scroll_opt_spec.lua index 7c97127193..a0127e306e 100644 --- a/test/functional/legacy/scroll_opt_spec.lua +++ b/test/functional/legacy/scroll_opt_spec.lua @@ -1202,14 +1202,9 @@ describe('smoothscroll', function() it('<<< marker shows with tabline, winbar and splits', function() screen:try_resize(40, 12) - screen:set_default_attr_ids({ - [1] = { foreground = Screen.colors.Blue1, bold = true }, - [2] = { reverse = true }, - [3] = { bold = true, reverse = true }, - [4] = { background = Screen.colors.LightMagenta }, - [5] = { bold = true }, - [31] = { foreground = Screen.colors.Fuchsia, bold = true }, - }) + screen:add_extra_attr_ids { + [100] = { foreground = Screen.colors.Magenta1, bold = true }, + } exec([[ call setline(1, ['Line' .. (' with some text'->repeat(7))]->repeat(7)) set smoothscroll scrolloff=0 @@ -1232,7 +1227,7 @@ describe('smoothscroll', function() exec('set showtabline=2') feed('<C-E>') screen:expect([[ - {5: }{31:2}{5:+ [No Name] }{2: }| + {5: }{100:2}{5:+ [No Name] }{2: }| {1:<<<}e text with some text with some text | with some text with some text | Line with some text with some text with | @@ -1248,7 +1243,7 @@ describe('smoothscroll', function() exec('set winbar=winbar') feed('<C-w>k<C-E>') screen:expect([[ - {5: }{31:2}{5:+ [No Name] }{2: }| + {5: }{100:2}{5:+ [No Name] }{2: }| {5:winbar }| {1:<<<}e text with some text with some text | ^with some text with some text | diff --git a/test/functional/legacy/search_spec.lua b/test/functional/legacy/search_spec.lua index db7d6bc34b..2678f11971 100644 --- a/test/functional/legacy/search_spec.lua +++ b/test/functional/legacy/search_spec.lua @@ -18,13 +18,6 @@ describe('search cmdline', function() clear() command('set nohlsearch inccommand=') screen = Screen.new(20, 3) - screen:set_default_attr_ids({ - inc = { reverse = true }, - err = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - more = { bold = true, foreground = Screen.colors.SeaGreen4 }, - tilde = { bold = true, foreground = Screen.colors.Blue1 }, - hl = { background = Screen.colors.Yellow }, - }) end) local function tenlines() @@ -60,57 +53,57 @@ describe('search cmdline', function() feed('/the') screen:expect([[ 1 | - 2 {inc:the}se | + 2 {2:the}se | /the^ | ]]) feed('<C-G>') screen:expect([[ 2 these | - 3 {inc:the} | + 3 {2:the} | /the^ | ]]) eq({ 0, 0, 0, 0 }, fn.getpos('"')) feed('<C-G>') screen:expect([[ 3 the | - 4 {inc:the}ir | + 4 {2:the}ir | /the^ | ]]) feed('<C-G>') screen:expect([[ 4 their | - 5 {inc:the}re | + 5 {2:the}re | /the^ | ]]) feed('<C-G>') screen:expect([[ 5 there | - 6 {inc:the}ir | + 6 {2:the}ir | /the^ | ]]) feed('<C-G>') screen:expect([[ 6 their | - 7 {inc:the} | + 7 {2:the} | /the^ | ]]) feed('<C-G>') screen:expect([[ 7 the | - 8 {inc:the}m | + 8 {2:the}m | /the^ | ]]) feed('<C-G>') screen:expect([[ 8 them | - 9 {inc:the}se | + 9 {2:the}se | /the^ | ]]) screen.bell = false feed('<C-G>') if wrapscan == 'wrapscan' then screen:expect([[ - 2 {inc:the}se | + 2 {2:the}se | 3 the | /the^ | ]]) @@ -118,7 +111,7 @@ describe('search cmdline', function() screen:expect { grid = [[ 8 them | - 9 {inc:the}se | + 9 {2:the}se | /the^ | ]], condition = function() @@ -136,7 +129,7 @@ describe('search cmdline', function() feed('?the') screen:expect([[ - 9 {inc:the}se | + 9 {2:the}se | 10 foobar | ?the^ | ]]) @@ -144,7 +137,7 @@ describe('search cmdline', function() if wrapscan == 'wrapscan' then feed('<C-G>') screen:expect([[ - 2 {inc:the}se | + 2 {2:the}se | 3 the | ?the^ | ]]) @@ -158,7 +151,7 @@ describe('search cmdline', function() feed('<C-G>') screen:expect { grid = [[ - 9 {inc:the}se | + 9 {2:the}se | 10 foobar | ?the^ | ]], @@ -176,13 +169,13 @@ describe('search cmdline', function() command('$') feed('?the') screen:expect([[ - 9 {inc:the}se | + 9 {2:the}se | 10 foobar | ?the^ | ]]) feed('<C-T>') screen:expect([[ - 8 {inc:the}m | + 8 {2:the}m | 9 these | ?the^ | ]]) @@ -195,7 +188,7 @@ describe('search cmdline', function() end end screen:expect([[ - 2 {inc:the}se | + 2 {2:the}se | 3 the | ?the^ | ]]) @@ -203,14 +196,14 @@ describe('search cmdline', function() feed('<C-T>') if wrapscan == 'wrapscan' then screen:expect([[ - 9 {inc:the}se | + 9 {2:the}se | 10 foobar | ?the^ | ]]) else screen:expect { grid = [[ - 2 {inc:the}se | + 2 {2:the}se | 3 the | ?the^ | ]], @@ -245,24 +238,24 @@ describe('search cmdline', function() feed('/the') screen:expect([[ 1 | - 2 {inc:the}se | + 2 {2:the}se | /the^ | ]]) feed('<C-L>') screen:expect([[ 1 | - 2 {inc:thes}e | + 2 {2:thes}e | /thes^ | ]]) feed('<C-G>') screen:expect([[ - 9 {inc:thes}e | + 9 {2:thes}e | 10 foobar | /thes^ | ]]) feed('<C-G>') screen:expect([[ - 2 {inc:thes}e | + 2 {2:thes}e | 3 the | /thes^ | ]]) @@ -278,18 +271,18 @@ describe('search cmdline', function() feed('/the') screen:expect([[ 1 | - 2 {inc:the}se | + 2 {2:the}se | /the^ | ]]) feed('<C-L>') screen:expect([[ 1 | - 2 {inc:thes}e | + 2 {2:thes}e | /thes^ | ]]) feed('<C-G>') screen:expect([[ - 9 {inc:thes}e | + 9 {2:thes}e | 10 foobar | /thes^ | ]]) @@ -309,41 +302,41 @@ describe('search cmdline', function() feed('/thei') screen:expect([[ 3 the | - 4 {inc:thei}r | + 4 {2:thei}r | /thei^ | ]]) -- Match from initial cursor position when modifying search feed('<BS>') screen:expect([[ 1 | - 2 {inc:the}se | + 2 {2:the}se | /the^ | ]]) -- New text advances to next match feed('s') screen:expect([[ 1 | - 2 {inc:thes}e | + 2 {2:thes}e | /thes^ | ]]) -- Stay on this match when deleting a character feed('<BS>') screen:expect([[ 1 | - 2 {inc:the}se | + 2 {2:the}se | /the^ | ]]) -- Advance to previous match feed('<C-T>') screen:expect([[ - 9 {inc:the}se | + 9 {2:the}se | 10 foobar | /the^ | ]]) -- Extend search to include next character feed('<C-L>') screen:expect([[ - 9 {inc:thes}e | + 9 {2:thes}e | 10 foobar | /thes^ | ]]) @@ -357,13 +350,13 @@ describe('search cmdline', function() feed('the') screen:expect([[ 1 | - 2 {inc:the}se | + 2 {2:the}se | /the^ | ]]) feed('\\>') screen:expect([[ 2 these | - 3 {inc:the} | + 3 {2:the} | /the\>^ | ]]) end) @@ -377,7 +370,7 @@ describe('search cmdline', function() feed('/the') screen:expect([[ 1 | - 2 {inc:the}se | + 2 {2:the}se | /the^ | ]]) @@ -385,7 +378,7 @@ describe('search cmdline', function() feed('<C-G>') screen:expect([[ 2 these | - 3 {inc:the} theother | + 3 {2:the} theother | /the^ | ]]) @@ -393,13 +386,13 @@ describe('search cmdline', function() feed('<C-G>') screen:expect([[ 2 these | - 3 the {inc:the}other | + 3 the {2:the}other | /the^ | ]]) feed('<C-G>') screen:expect([[ 2 these | - 3 the theo{inc:the}r | + 3 the theo{2:the}r | /the^ | ]]) @@ -407,20 +400,20 @@ describe('search cmdline', function() feed('<C-T>') screen:expect([[ 2 these | - 3 the {inc:the}other | + 3 the {2:the}other | /the^ | ]]) feed('<C-T>') screen:expect([[ 2 these | - 3 {inc:the} theother | + 3 {2:the} theother | /the^ | ]]) -- Previous match, different line feed('<C-T>') screen:expect([[ - 2 {inc:the}se | + 2 {2:the}se | 3 the theother | /the^ | ]]) @@ -436,7 +429,7 @@ describe('search cmdline', function() 7 the | 8 them | 9 these | - 10 {inc:foo}bar | + 10 {2:foo}bar | /foo^ | ]]) feed('<BS>') @@ -445,7 +438,7 @@ describe('search cmdline', function() 7 the | 8 them | 9 these | - 10 {inc:fo}obar | + 10 {2:fo}obar | /fo^ | ]]) feed('<CR>') @@ -476,7 +469,7 @@ describe('search cmdline', function() feed('/foo') screen:expect([[ 9 these | - 10 {inc:foo}bar | + 10 {2:foo}bar | /foo^ | ]]) feed('<C-W>') @@ -488,8 +481,8 @@ describe('search cmdline', function() feed('<CR>') screen:expect([[ / | - {err:E35: No previous regular expression} | - {more:Press ENTER or type command to continue}^ | + {9:E35: No previous regular expression} | + {6:Press ENTER or type command to continue}^ | ]]) feed('<CR>') eq({ @@ -613,9 +606,9 @@ describe('search cmdline', function() feed(':sort ni u /on') screen:expect([[ - another {inc:on}e 2 | - that {hl:on}e 3 | - the {hl:on}e 1 | + another {2:on}e 2 | + that {10:on}e 3 | + the {10:on}e 1 | :sort ni u /on^ | ]]) feed('<esc>') @@ -629,27 +622,27 @@ describe('search cmdline', function() feed(':vimgrep on') screen:expect([[ - another {inc:on}e 2 | - that {hl:on}e 3 | - the {hl:on}e 1 | + another {2:on}e 2 | + that {10:on}e 3 | + the {10:on}e 1 | :vimgrep on^ | ]]) feed('<esc>') feed(':vimg /on/ *.txt') screen:expect([[ - another {inc:on}e 2 | - that {hl:on}e 3 | - the {hl:on}e 1 | + another {2:on}e 2 | + that {10:on}e 3 | + the {10:on}e 1 | :vimg /on/ *.txt^ | ]]) feed('<esc>') feed(':vimgrepadd "\\<LT>on') screen:expect([[ - another {inc:on}e 2 | - that {hl:on}e 3 | - the {hl:on}e 1 | + another {2:on}e 2 | + that {10:on}e 3 | + the {10:on}e 1 | :vimgrepadd "\<on^ | ]]) feed('<esc>') @@ -657,7 +650,7 @@ describe('search cmdline', function() feed(':lv "tha') screen:expect([[ another one 2 | - {inc:tha}t one 3 | + {2:tha}t one 3 | the one 1 | :lv "tha^ | ]]) @@ -665,9 +658,9 @@ describe('search cmdline', function() feed(':lvimgrepa "the" **/*.txt') screen:expect([[ - ano{inc:the}r one 2 | + ano{2:the}r one 2 | that one 3 | - {hl:the} one 1 | + {10:the} one 1 | :lvimgrepa "the" **/*.txt^ | ]]) feed('<esc>') @@ -718,7 +711,7 @@ describe('search cmdline', function() call feedkeys("2\<C-E>", 't') ]]) local s = [[ - {tilde:<<<} 18 19 20 21 22 2| + {1:<<<} 18 19 20 21 22 2| ^3 24 | |*4 ]] @@ -726,13 +719,13 @@ describe('search cmdline', function() feed('/xx') screen:expect([[ |*4 - {inc:xx}x | + {2:xx}x | /xx^ | ]]) feed('x') screen:expect([[ |*4 - {inc:xxx} | + {2:xxx} | /xxx^ | ]]) feed('<Esc>') @@ -764,13 +757,14 @@ describe('Search highlight', function() -- oldtest: Test_hlsearch_and_visual() it('is combined with Visual highlight vim-patch:8.2.2797', function() local screen = Screen.new(40, 6) - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue }, -- NonText - [2] = { bold = true }, -- ModeMsg, Search - [3] = { background = Screen.colors.LightGrey, foreground = Screen.colors.Black }, -- Visual - [4] = { background = Screen.colors.Yellow, bold = true }, -- Search - [5] = { background = Screen.colors.LightGrey, bold = true, foreground = Screen.colors.Black }, - }) + screen:add_extra_attr_ids { + [100] = { + foreground = Screen.colors.Black, + bold = true, + background = Screen.colors.LightGrey, + }, + [101] = { bold = true, background = Screen.colors.Yellow }, + } exec([[ set hlsearch noincsearch call setline(1, repeat(["xxx yyy zzz"], 3)) @@ -780,11 +774,11 @@ describe('Search highlight', function() ]]) feed('vjj') screen:expect([[ - xxx {4:y}{5:yy}{3: zzz} | - {3:xxx }{5:yyy}{3: zzz} | - {3:xxx }{5:y}{4:^yy} zzz | + xxx {101:y}{100:yy}{17: zzz} | + {17:xxx }{100:yyy}{17: zzz} | + {17:xxx }{100:y}{101:^yy} zzz | {1:~ }|*2 - {2:-- VISUAL --} | + {5:-- VISUAL --} | ]]) end) end) diff --git a/test/functional/legacy/search_stat_spec.lua b/test/functional/legacy/search_stat_spec.lua index 1ffae89b0c..1ba55e583b 100644 --- a/test/functional/legacy/search_stat_spec.lua +++ b/test/functional/legacy/search_stat_spec.lua @@ -8,13 +8,6 @@ describe('search stat', function() before_each(function() clear() screen = Screen.new(30, 10) - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue }, -- NonText - [2] = { background = Screen.colors.Yellow }, -- Search - [3] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey }, -- Folded - [4] = { reverse = true }, -- IncSearch, TabLineFill - [5] = { foreground = Screen.colors.Red }, -- WarningMsg - }) end) -- oldtest: Test_search_stat_screendump() @@ -32,7 +25,7 @@ describe('search stat', function() ]]) screen:expect([[ foobar | - {2:^find this} | + {10:^find this} | fooooobar | foba | foobar |*2 @@ -45,7 +38,7 @@ describe('search stat', function() feed('gg0n') screen:expect([[ foobar | - {2:^find this} | + {10:^find this} | fooooobar | foba | foobar |*2 @@ -68,7 +61,7 @@ describe('search stat', function() ]]) screen:expect([[ if | - {3:^+-- 2 lines: foo·············}| + {13:^+-- 2 lines: foo·············}| endif | | {1:~ }|*5 @@ -91,16 +84,16 @@ describe('search stat', function() feed('/dog<CR>') screen:expect([[ int cat; | - int {2:^dog}; | - cat = {2:dog}; | + int {10:^dog}; | + cat = {10:dog}; | {1:~ }|*6 /dog [1/2] | ]]) feed('G0gD') screen:expect([[ - int {2:^cat}; | + int {10:^cat}; | int dog; | - {2:cat} = dog; | + {10:cat} = dog; | {1:~ }|*6 | ]]) @@ -128,30 +121,30 @@ describe('search stat', function() feed('/abc') screen:expect([[ - {4: }| - {2:abc}--c | - --------{4:abc} | - --{2:abc} | + {2: }| + {10:abc}--c | + --------{2:abc} | + --{10:abc} | {1:~ }|*5 /abc^ | ]]) feed('<C-G>') screen:expect([[ - {4:3/3 }| - {2:abc}--c | - --------{2:abc} | - --{4:abc} | + {2:3/3 }| + {10:abc}--c | + --------{10:abc} | + --{2:abc} | {1:~ }|*5 /abc^ | ]]) feed('<C-G>') screen:expect([[ - {4:1/3 }| - {4:abc}--c | - --------{2:abc} | - --{2:abc} | + {2:1/3 }| + {2:abc}--c | + --------{10:abc} | + --{10:abc} | {1:~ }|*5 /abc^ | ]]) @@ -167,7 +160,7 @@ describe('search stat', function() feed('*') screen:expect([[ - {2:^test} | + {10:^test} | | {1:~ }|*7 /\<test\> [1/1] | @@ -175,7 +168,7 @@ describe('search stat', function() feed('N') screen:expect([[ - {2:^test} | + {10:^test} | | {1:~ }|*7 ?\<test\> [1/1] | @@ -185,10 +178,10 @@ describe('search stat', function() feed('N') -- shows "Search Hit Bottom.." screen:expect([[ - {2:^test} | + {10:^test} | | {1:~ }|*7 - {5:search hit TOP, continuing at BOTTOM} | + {19:search hit TOP, continuing at BOTTOM} | ]]) end) end) diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua index fb55611198..cb3b436f9e 100644 --- a/test/functional/lua/commands_spec.lua +++ b/test/functional/lua/commands_spec.lua @@ -173,8 +173,13 @@ describe(':lua', function() exec_lua('x = 5') eq('5', exec_capture(':lua =x')) eq('5', exec_capture('=x')) + exec_lua('x = "5"') + eq('"5"', exec_capture(':lua =x')) + eq('"5"', exec_capture('=x')) exec_lua("function x() return 'hello' end") - eq('hello', exec_capture(':lua = x()')) + eq('"hello"', exec_capture(':lua = x()')) + exec_lua("function x() return 'hello ' end") + eq('"hello "', exec_capture(':lua = x()')) exec_lua('x = {a = 1, b = 2}') eq('{\n a = 1,\n b = 2\n}', exec_capture(':lua =x')) exec_lua(function() @@ -189,14 +194,14 @@ describe(':lua', function() eq( dedent [[ true - Return value]], + "Return value"]], exec_capture(':lua =x(true)') ) eq( dedent [[ false nil - Error message]], + "Error message"]], exec_capture('=x(false)') ) end) diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index a19f558ef2..e9636742d7 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -2231,6 +2231,110 @@ describe('vim.diagnostic', function() eq('Another error there!', result[2][4].virt_lines[1][3][1]) end) + it('highlights multiple diagnostics in a single line by default', function() + local result = exec_lua(function() + vim.api.nvim_buf_set_lines( + _G.diagnostic_bufnr, + 0, + -1, + false, + { 'def foo(x: int, /, y: str, *, z: float) -> None: ...' } + ) + + vim.diagnostic.config({ virtual_lines = true }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!', 0, 8, 0, 9, 'foo_server'), + _G.make_error('Another error there!', 0, 19, 0, 20, 'foo_server'), + _G.make_error('And another one!', 0, 30, 0, 31, 'foo_server'), + }) + + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks + end) + + --[[ + |def foo(x: int, /, y: str, *, z: float) -> None: ... + | │ │ └──── And another one! + | │ └──── Another error there! + | └──── Error here! + | ^ col=8 + | ^ col=19 + | ^ col=30 + + 11 cols between each diagnostic after the first one (10 spaces + |) + ]] + + eq(1, #result) + local virt_lines = result[1][4].virt_lines + eq(8, virt_lines[1][1][1]:len()) -- first space + eq(10, virt_lines[1][3][1]:len()) -- second space + eq(10, virt_lines[1][5][1]:len()) -- third space + eq('And another one!', virt_lines[1][7][1]) + eq(8, virt_lines[2][1][1]:len()) -- first space + eq(10, virt_lines[2][3][1]:len()) -- second space + eq('Another error there!', virt_lines[2][5][1]) + eq(8, virt_lines[3][1][1]:len()) -- first space + eq('Error here!', virt_lines[3][3][1]) + end) + + it('highlights overlapping diagnostics on a single line', function() + local result = exec_lua(function() + vim.diagnostic.config({ virtual_lines = true }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!', 0, 10, 0, 11, 'foo_server'), + _G.make_error('Another error here!', 0, 10, 0, 11, 'foo_server'), + }) + + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks + end) + + --[[ + |1234567890x + | ├──── Another error here! + | └──── Error here! + ]] + + eq(1, #result) + local virt_lines = result[1][4].virt_lines + eq(10, virt_lines[1][1][1]:len()) -- first space + eq('├──── ', virt_lines[1][2][1]) + eq('Another error here!', virt_lines[1][3][1]) + eq(10, virt_lines[2][1][1]:len()) -- second space + eq('└──── ', virt_lines[2][2][1]) + eq('Error here!', virt_lines[2][3][1]) + end) + + it('handles multi-line diagnostic message', function() + local result = exec_lua(function() + vim.diagnostic.config({ virtual_lines = true }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!\ngot another line', 0, 10, 0, 11, 'foo_server'), + }) + + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks + end) + + --[[ + |1234567890x + | └──── Error here! + | got another line + ]] + + eq(1, #result) + local virt_lines = result[1][4].virt_lines + eq(10, virt_lines[1][1][1]:len()) -- first space + eq('└──── ', virt_lines[1][2][1]) + eq('Error here!', virt_lines[1][3][1]) + eq(10, virt_lines[2][1][1]:len()) -- second line space + eq(6, virt_lines[2][2][1]:len()) -- extra padding + eq('got another line', virt_lines[2][3][1]) + end) + it('can highlight diagnostics only in the current line', function() local result = exec_lua(function() vim.api.nvim_win_set_cursor(0, { 1, 0 }) diff --git a/test/functional/lua/inspector_spec.lua b/test/functional/lua/inspector_spec.lua index 3a1263f6a3..89a103f51f 100644 --- a/test/functional/lua/inspector_spec.lua +++ b/test/functional/lua/inspector_spec.lua @@ -12,7 +12,7 @@ describe('vim.inspect_pos', function() end) it('it returns items', function() - local buf, items, other_buf_syntax = exec_lua(function() + local buf, ns1, ns2, items, other_buf_syntax = exec_lua(function() local buf = vim.api.nvim_create_buf(true, false) local buf1 = vim.api.nvim_create_buf(true, false) local ns1 = vim.api.nvim_create_namespace('ns1') @@ -25,7 +25,7 @@ describe('vim.inspect_pos', function() vim.api.nvim_buf_set_extmark(buf, ns1, 0, 10, { hl_group = 'Normal' }) vim.api.nvim_buf_set_extmark(buf, ns2, 0, 10, { hl_group = 'Normal' }) vim.cmd('syntax on') - return buf, vim.inspect_pos(0, 0, 10), vim.inspect_pos(buf1, 0, 10).syntax + return buf, ns1, ns2, vim.inspect_pos(0, 0, 10), vim.inspect_pos(buf1, 0, 10).syntax end) eq('', eval('v:errmsg')) @@ -40,12 +40,12 @@ describe('vim.inspect_pos', function() end_row = 0, id = 1, ns = 'ns1', - ns_id = 1, + ns_id = ns1, opts = { hl_eol = false, hl_group = 'Normal', hl_group_link = 'Normal', - ns_id = 1, + ns_id = ns1, priority = 4096, right_gravity = true, }, @@ -57,12 +57,12 @@ describe('vim.inspect_pos', function() end_row = 0, id = 1, ns = '', - ns_id = 2, + ns_id = ns2, opts = { hl_eol = false, hl_group = 'Normal', hl_group_link = 'Normal', - ns_id = 2, + ns_id = ns2, priority = 4096, right_gravity = true, }, diff --git a/test/functional/lua/loop_spec.lua b/test/functional/lua/loop_spec.lua index 8df2900368..fbf70d7be7 100644 --- a/test/functional/lua/loop_spec.lua +++ b/test/functional/lua/loop_spec.lua @@ -194,4 +194,52 @@ describe('vim.uv', function() feed('<cr>') n.assert_alive() end) + + it("doesn't crash on async callbacks throwing nil error", function() + local screen = Screen.new(50, 4) + + exec_lua(function() + _G.idle = vim.uv.new_idle() + _G.idle:start(function() + _G.idle:stop() + error() + end) + end) + + screen:expect([[ + {3: }| + {9:Error executing callback:} | + {9:[NULL]} | + {6:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + + exec_lua(function() + _G.idle:close() + end) + end) + + it("doesn't crash on async callbacks throwing object as an error", function() + local screen = Screen.new(50, 4) + + exec_lua(function() + _G.idle = vim.uv.new_idle() + _G.idle:start(function() + _G.idle:stop() + error(_G.idle) -- userdata with __tostring method + end) + end) + + screen:expect([[ + {3: }| + {9:Error executing callback:} | + {9:uv_idle_t: 0x{MATCH:%w+}} | + {6:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + + exec_lua(function() + _G.idle:close() + end) + end) end) diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua index ddb10127e4..8159a4c91c 100644 --- a/test/functional/lua/ui_event_spec.lua +++ b/test/functional/lua/ui_event_spec.lua @@ -372,7 +372,7 @@ describe('vim.ui_attach', function() | {3: }| {9:Excessive errors in vim.ui_attach() call}| - {9:back from ns: 1.} | + {9:back from ns: 2.} | {100:Press ENTER or type command to continue}^ | ]]) feed('<cr>') @@ -419,7 +419,7 @@ describe('vim.ui_attach', function() | {3: }| {9:Excessive errors in vim.ui_attach() call}| - {9:back from ns: 2.} | + {9:back from ns: 3.} | {100:Press ENTER or type command to continue}^ | ]]) end) diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua index ccefc3cb99..7bfb354c2e 100644 --- a/test/functional/plugin/lsp/utils_spec.lua +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -368,6 +368,7 @@ describe('vim.lsp.util', function() ]]) -- Correct height when float inherits 'conceallevel' >= 2 #32639 command('close | set conceallevel=2') + feed('<Ignore>') -- Prevent CursorMoved closing the next float immediately exec_lua([[ vim.lsp.util.open_floating_preview({ '```lua', 'local foo', '```' }, 'markdown', { border = 'single', @@ -382,5 +383,47 @@ describe('vim.lsp.util', function() {1:~ }|*9 | ]]) + -- This tests the valid winline code path (why doesn't the above?). + exec_lua([[ + vim.cmd.only() + vim.lsp.util.open_floating_preview({ 'foo', '```lua', 'local bar', '```' }, 'markdown', { + border = 'single', + focus = false, + }) + ]]) + feed('<C-W>wG') + screen:expect([[ + | + ┌─────────┐{1: }| + │{4:foo }│{1: }| + │{100:^local}{101: }{102:bar}│{1: }| + └─────────┘{1: }| + {1:~ }|*8 + | + ]]) + end) + + it('open_floating_preview height does not exceed max_height', function() + local screen = Screen.new() + exec_lua([[ + vim.lsp.util.open_floating_preview(vim.fn.range(1, 10), 'markdown', { + border = 'single', + width = 5, + max_height = 5, + focus = false, + }) + ]]) + screen:expect([[ + ^ | + ┌─────┐{1: }| + │{4:1 }│{1: }| + │{4:2 }│{1: }| + │{4:3 }│{1: }| + │{4:4 }│{1: }| + │{4:5 }│{1: }| + └─────┘{1: }| + {1:~ }|*5 + | + ]]) end) end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 17e3fbbf70..22c9e4658f 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -2526,7 +2526,7 @@ describe('LSP', function() ) end) - it('Supports file creation with CreateFile payload', function() + it('supports file creation with CreateFile payload', function() local tmpfile = tmpname(false) local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) local edit = { @@ -2544,7 +2544,7 @@ describe('LSP', function() end) it( - 'Supports file creation in folder that needs to be created with CreateFile payload', + 'supports file creation in folder that needs to be created with CreateFile payload', function() local tmpfile = tmpname(false) .. '/dummy/x/' local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) @@ -2647,7 +2647,7 @@ describe('LSP', function() describe('lsp.util.rename', function() local pathsep = n.get_pathsep() - it('Can rename an existing file', function() + it('can rename an existing file', function() local old = tmpname() write_file(old, 'Test content') local new = tmpname(false) @@ -2668,7 +2668,7 @@ describe('LSP', function() os.remove(new) end) - it('Can rename a directory', function() + it('can rename a directory', function() -- only reserve the name, file must not exist for the test scenario local old_dir = tmpname(false) local new_dir = tmpname(false) @@ -2695,7 +2695,7 @@ describe('LSP', function() os.remove(new_dir) end) - it('Does not touch buffers that do not match path prefix', function() + it('does not touch buffers that do not match path prefix', function() local old = tmpname(false) local new = tmpname(false) n.mkdir_p(old) @@ -2730,7 +2730,7 @@ describe('LSP', function() end) it( - 'Does not rename file if target exists and ignoreIfExists is set or overwrite is false', + 'does not rename file if target exists and ignoreIfExists is set or overwrite is false', function() local old = tmpname() write_file(old, 'Old File') @@ -2753,7 +2753,7 @@ describe('LSP', function() end ) - it('Maintains undo information for loaded buffer', function() + it('maintains undo information for loaded buffer', function() local old = tmpname() write_file(old, 'line') local new = tmpname(false) @@ -2777,7 +2777,7 @@ describe('LSP', function() eq(true, undo_kept) end) - it('Maintains undo information for unloaded buffer', function() + it('maintains undo information for unloaded buffer', function() local old = tmpname() write_file(old, 'line') local new = tmpname(false) @@ -2798,7 +2798,7 @@ describe('LSP', function() eq(true, undo_kept) end) - it('Does not rename file when it conflicts with a buffer without file', function() + it('does not rename file when it conflicts with a buffer without file', function() local old = tmpname() write_file(old, 'Old File') local new = tmpname(false) @@ -2817,7 +2817,7 @@ describe('LSP', function() eq('Old File', read_file(old)) end) - it('Does override target if overwrite is true', function() + it('does override target if overwrite is true', function() local old = tmpname() write_file(old, 'Old file') local new = tmpname() @@ -2833,7 +2833,7 @@ describe('LSP', function() end) describe('lsp.util.locations_to_items', function() - it('Convert Location[] to items', function() + it('convert Location[] to items', function() local expected_template = { { filename = '/fake/uri', @@ -2879,7 +2879,7 @@ describe('LSP', function() end end) - it('Convert LocationLink[] to items', function() + it('convert LocationLink[] to items', function() local expected = { { filename = '/fake/uri', @@ -2926,7 +2926,7 @@ describe('LSP', function() describe('lsp.util.symbols_to_items', function() describe('convert DocumentSymbol[] to items', function() - it('DocumentSymbol has children', function() + it('documentSymbol has children', function() local expected = { { col = 1, @@ -3047,7 +3047,7 @@ describe('LSP', function() ) end) - it('DocumentSymbol has no children', function() + it('documentSymbol has no children', function() local expected = { { col = 1, @@ -4387,7 +4387,7 @@ describe('LSP', function() end) describe('vim.lsp.buf.code_action', function() - it('Calls client side command if available', function() + it('calls client side command if available', function() local client --- @type vim.lsp.Client local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, @@ -4431,7 +4431,7 @@ describe('LSP', function() } end) - it('Calls workspace/executeCommand if no client side command', function() + it('calls workspace/executeCommand if no client side command', function() local client --- @type vim.lsp.Client local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, @@ -4472,7 +4472,7 @@ describe('LSP', function() }) end) - it('Filters and automatically applies action if requested', function() + it('filters and automatically applies action if requested', function() local client --- @type vim.lsp.Client local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, @@ -4547,7 +4547,7 @@ describe('LSP', function() } end) - it('Fallback to command execution on resolve error', function() + it('fallback to command execution on resolve error', function() clear() exec_lua(create_server_definition) local result = exec_lua(function() @@ -4591,17 +4591,62 @@ describe('LSP', function() eq('workspace/executeCommand', result[5].method) eq('command:1', result[5].params.command) end) + + it('resolves command property', function() + clear() + exec_lua(create_server_definition) + local result = exec_lua(function() + local server = _G._create_server({ + capabilities = { + executeCommandProvider = { + commands = { 'command:1' }, + }, + codeActionProvider = { + resolveProvider = true, + }, + }, + handlers = { + ['textDocument/codeAction'] = function(_, _, callback) + callback(nil, { + { title = 'Code Action 1' }, + }) + end, + ['codeAction/resolve'] = function(_, _, callback) + callback(nil, { + title = 'Code Action 1', + command = { + title = 'Command 1', + command = 'command:1', + }, + }) + end, + }, + }) + + local client_id = assert(vim.lsp.start({ + name = 'dummy', + cmd = server.cmd, + })) + + vim.lsp.buf.code_action({ apply = true }) + vim.lsp.stop_client(client_id) + return server.messages + end) + eq('codeAction/resolve', result[4].method) + eq('workspace/executeCommand', result[5].method) + eq('command:1', result[5].params.command) + end) end) describe('vim.lsp.commands', function() - it('Accepts only string keys', function() + it('accepts only string keys', function() matches( '.*The key for commands in `vim.lsp.commands` must be a string', pcall_err(exec_lua, 'vim.lsp.commands[1] = function() end') ) end) - it('Accepts only function values', function() + it('accepts only function values', function() matches( '.*Command added to `vim.lsp.commands` must be a function', pcall_err(exec_lua, 'vim.lsp.commands.dummy = 10') @@ -4835,7 +4880,7 @@ describe('LSP', function() end) describe('vim.lsp.buf.format', function() - it('Aborts with notify if no client matches filter', function() + it('aborts with notify if no client matches filter', function() local client --- @type vim.lsp.Client test_rpc_server { test_name = 'basic_init', @@ -4861,7 +4906,7 @@ describe('LSP', function() } end) - it('Sends textDocument/formatting request to format buffer', function() + it('sends textDocument/formatting request to format buffer', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, { NIL, {}, { method = 'start', client_id = 1 } }, @@ -4895,7 +4940,7 @@ describe('LSP', function() } end) - it('Sends textDocument/rangeFormatting request to format a range', function() + it('sends textDocument/rangeFormatting request to format a range', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, { NIL, {}, { method = 'start', client_id = 1 } }, @@ -4936,7 +4981,7 @@ describe('LSP', function() } end) - it('Sends textDocument/rangesFormatting request to format multiple ranges', function() + it('sends textDocument/rangesFormatting request to format multiple ranges', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, { NIL, {}, { method = 'start', client_id = 1 } }, @@ -4983,7 +5028,7 @@ describe('LSP', function() } end) - it('Can format async', function() + it('can format async', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, { NIL, {}, { method = 'start', client_id = 1 } }, @@ -5112,7 +5157,7 @@ describe('LSP', function() eq(expected_range, result[5].params.range) end) - it('Aborts with notify if no clients support requested method', function() + it('aborts with notify if no clients support requested method', function() exec_lua(create_server_definition) exec_lua(function() vim.notify = function(msg, _) diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index f2d679bd5d..599adc87e1 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -333,60 +333,151 @@ describe(':terminal buffer', function() command('bdelete!') end) - it('emits TermRequest events #26972', function() - local term = api.nvim_open_term(0, {}) - local termbuf = api.nvim_get_current_buf() - - -- Test that <abuf> is the terminal buffer, not the current buffer - command('au TermRequest * let g:termbuf = +expand("<abuf>")') - command('wincmd p') - - -- cwd will be inserted in a file URI, which cannot contain backs - local cwd = t.fix_slashes(fn.getcwd()) - local parent = cwd:match('^(.+/)') - local expected = '\027]7;file://host' .. parent - api.nvim_chan_send(term, string.format('%s\027\\', expected)) - eq(expected, eval('v:termrequest')) - eq(termbuf, eval('g:termbuf')) - end) - - it('emits TermRequest events for APC', function() - local term = api.nvim_open_term(0, {}) - - -- cwd will be inserted in a file URI, which cannot contain backs - local cwd = t.fix_slashes(fn.getcwd()) - local parent = cwd:match('^(.+/)') - local expected = '\027_Gfile://host' .. parent - api.nvim_chan_send(term, string.format('%s\027\\', expected)) - eq(expected, eval('v:termrequest')) - end) - - it('TermRequest synchronization #27572', function() - command('autocmd! nvim.terminal TermRequest') - local term = exec_lua([[ - _G.input = {} - local term = vim.api.nvim_open_term(0, { - on_input = function(_, _, _, data) - table.insert(_G.input, data) - end, - force_crlf = false, - }) - vim.api.nvim_create_autocmd('TermRequest', { - callback = function(args) - if args.data == '\027]11;?' then - table.insert(_G.input, '\027]11;rgb:0000/0000/0000\027\\') + describe('TermRequest', function() + it('emits events #26972', function() + local term = api.nvim_open_term(0, {}) + local termbuf = api.nvim_get_current_buf() + + -- Test that <abuf> is the terminal buffer, not the current buffer + command('au TermRequest * let g:termbuf = +expand("<abuf>")') + command('wincmd p') + + -- cwd will be inserted in a file URI, which cannot contain backs + local cwd = t.fix_slashes(fn.getcwd()) + local parent = cwd:match('^(.+/)') + local expected = '\027]7;file://host' .. parent + api.nvim_chan_send(term, string.format('%s\027\\', expected)) + eq(expected, eval('v:termrequest')) + eq(termbuf, eval('g:termbuf')) + end) + + it('emits events for APC', function() + local term = api.nvim_open_term(0, {}) + + -- cwd will be inserted in a file URI, which cannot contain backs + local cwd = t.fix_slashes(fn.getcwd()) + local parent = cwd:match('^(.+/)') + local expected = '\027_Gfile://host' .. parent + api.nvim_chan_send(term, string.format('%s\027\\', expected)) + eq(expected, eval('v:termrequest')) + end) + + it('synchronization #27572', function() + command('autocmd! nvim.terminal TermRequest') + local term = exec_lua([[ + _G.input = {} + local term = vim.api.nvim_open_term(0, { + on_input = function(_, _, _, data) + table.insert(_G.input, data) + end, + force_crlf = false, + }) + vim.api.nvim_create_autocmd('TermRequest', { + callback = function(args) + if args.data.sequence == '\027]11;?' then + table.insert(_G.input, '\027]11;rgb:0000/0000/0000\027\\') + end end - end - }) - return term - ]]) - api.nvim_chan_send(term, '\027]11;?\007\027[5n\027]11;?\007\027[5n') - eq({ - '\027]11;rgb:0000/0000/0000\027\\', - '\027[0n', - '\027]11;rgb:0000/0000/0000\027\\', - '\027[0n', - }, exec_lua('return _G.input')) + }) + return term + ]]) + api.nvim_chan_send(term, '\027]11;?\007\027[5n\027]11;?\007\027[5n') + eq({ + '\027]11;rgb:0000/0000/0000\027\\', + '\027[0n', + '\027]11;rgb:0000/0000/0000\027\\', + '\027[0n', + }, exec_lua('return _G.input')) + end) + + it('works with vim.wait() from another autocommand #32706', function() + command('autocmd! nvim.terminal TermRequest') + exec_lua([[ + local term = vim.api.nvim_open_term(0, {}) + vim.api.nvim_create_autocmd('TermRequest', { + buffer = 0, + callback = function(ev) + _G.sequence = ev.data.sequence + _G.v_termrequest = vim.v.termrequest + end, + }) + vim.api.nvim_create_autocmd('TermEnter', { + buffer = 0, + callback = function() + vim.api.nvim_chan_send(term, '\027]11;?\027\\') + _G.result = vim.wait(3000, function() + local expected = '\027]11;?' + return _G.sequence == expected and _G.v_termrequest == expected + end) + end, + }) + ]]) + feed('i') + retry(nil, 4000, function() + eq(true, exec_lua('return _G.result')) + end) + end) + + it('includes cursor position #31609', function() + command('autocmd! nvim.terminal TermRequest') + local screen = Screen.new(50, 10) + local term = exec_lua([[ + _G.cursor = {} + local term = vim.api.nvim_open_term(0, {}) + vim.api.nvim_create_autocmd('TermRequest', { + callback = function(args) + _G.cursor = args.data.cursor + end + }) + return term + ]]) + -- Enter terminal mode so that the cursor follows the output + feed('a') + + -- Put some lines into the scrollback. This tests the conversion from terminal line to buffer + -- line. + api.nvim_chan_send(term, string.rep('>\n', 20)) + screen:expect([[ + > |*8 + ^ | + {5:-- TERMINAL --} | + ]]) + + -- Emit an OSC escape sequence + api.nvim_chan_send(term, 'Hello\nworld!\027]133;D\027\\') + screen:expect([[ + > |*7 + Hello | + world!^ | + {5:-- TERMINAL --} | + ]]) + eq({ 22, 6 }, exec_lua('return _G.cursor')) + end) + + it('does not cause hang in vim.wait() #32753', function() + local screen = Screen.new(50, 10) + + exec_lua(function() + local term = vim.api.nvim_open_term(0, {}) + + -- Write OSC sequence with pending scrollback. TermRequest will + -- reschedule itself onto an event queue until the pending scrollback is + -- processed (i.e. the terminal is refreshed). + vim.api.nvim_chan_send(term, string.format('%s\027]133;;\007', string.rep('a\n', 100))) + + -- vim.wait() drains the event queue. The terminal won't be refreshed + -- until the event queue is empty. This test ensures that TermRequest + -- does not continuously reschedule itself onto the same event queue, + -- causing an infinite loop. + vim.wait(100) + end) + + screen:expect([[ + ^a | + a |*8 + | + ]]) + end) end) it('no heap-buffer-overflow when using jobstart("echo",{term=true}) #3161', function() diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index 8ebad14f15..93e55bb103 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -149,6 +149,73 @@ describe(':terminal cursor', function() |*5 {3:-- TERMINAL --} | ]]) + + -- Cursor is hidden; now request to show it while in a TermLeave autocmd. + -- Process events (via :sleep) to handle the escape sequence now. + command([[autocmd TermLeave * ++once call chansend(&channel, "\e[?25h") | sleep 1m]]) + feed([[<C-\><C-N>]]) -- Exit terminal mode; cursor should not remain hidden + screen:expect([[ + tty ready | + ^ | + |*5 + ]]) + + command('bwipeout! | let chan = nvim_open_term(0, {})') + feed('i') + -- Hide the cursor, switch to a non-terminal buffer, then show the cursor; it shouldn't remain + -- hidden after we're kicked out of terminal mode in the new buffer. + -- Must ensure these actions happen within the same terminal_execute call. The stream is + -- internal, so polling the event loop isn't necessary (terminal_receive is directly called). + command([[call chansend(chan, "\e[?25l") | new floob | call chansend(chan, "\e[?25h")]]) + screen:expect([[ + ^ | + {4:~ }| + {5:floob }| + |*2 + {18:[Scratch] }| + | + ]]) + + feed('<C-W>pi') + screen:expect([[ + | + {4:~ }| + {1:floob }| + ^ | + | + {17:[Scratch] }| + {3:-- TERMINAL --} | + ]]) + end) + + it('becomes visible on TermLeave if hidden immediately by events #32456', function() + skip(is_os('win'), '#31587') + -- Reproducing the issue is quite fragile; it's easiest done in a lone test case like this + -- with no prior commands. + feed([[<C-\><C-N>]]) + screen:expect([[ + tty ready | + ^ | + |*5 + ]]) + + -- Hide the cursor such that the escape sequence is processed as a side effect of showmode in + -- terminal_enter handling events (skip_showmode -> char_avail -> vpeekc -> os_breakcheck). + -- This requires a particular set of actions; :startinsert repros better than feed('i') here. + hide_cursor() + command('mode | startinsert') + screen:expect([[ + tty ready | + |*5 + {3:-- TERMINAL --} | + ]]) + + feed([[<C-\><C-N>]]) + screen:expect([[ + tty ready | + ^ | + |*5 + ]]) end) end) @@ -358,55 +425,50 @@ describe(':terminal cursor', function() eq(error_hl_id, screen._mode_info[terminal_mode_idx].hl_id) end) - it('restores visibility on TermLeave #32456', function() - skip(is_os('win'), '#31587') - feed([[<C-\><C-N>]]) -- Exit terminal mode - screen:expect([[ - tty ready | - ^ | - |*5 - ]]) - - tt.hide_cursor() - -- :startinsert repros the issue more reliably than feed('i') - command('mode | startinsert') - screen:expect([[ - tty ready | - |*5 - {3:-- TERMINAL --} | - ]]) - - feed([[<C-\><C-N>]]) -- Exit terminal mode - screen:expect([[ - tty ready | - ^ | - |*5 + it('uses the correct attributes', function() + feed([[<C-\><C-N>]]) + command([[ + bwipeout! + let chan1 = nvim_open_term(0, {}) + vnew + let chan2 = nvim_open_term(0, {}) ]]) - feed('i') screen:expect([[ - tty ready | - |*5 + ^ │ | + │ |*4 + {17:[Scratch] }{18:[Scratch] }| {3:-- TERMINAL --} | ]]) + eq('block', screen._mode_info[terminal_mode_idx].cursor_shape) + eq(500, screen._mode_info[terminal_mode_idx].blinkon) + eq(500, screen._mode_info[terminal_mode_idx].blinkoff) - -- 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 - ]]) + -- Modify cursor in the non-current terminal; should not affect this cursor. + command([[call chansend(chan1, "\e[4 q")]]) + screen:expect_unchanged() + eq('block', screen._mode_info[terminal_mode_idx].cursor_shape) + eq(500, screen._mode_info[terminal_mode_idx].blinkon) + eq(500, screen._mode_info[terminal_mode_idx].blinkoff) - feed('i') + -- Modify cursor in the current terminal. + command([[call chansend(chan2, "\e[6 q")]]) + screen:expect_unchanged() + eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape) + eq(0, screen._mode_info[terminal_mode_idx].blinkon) + eq(0, screen._mode_info[terminal_mode_idx].blinkoff) + + -- Check the cursor in the other terminal reflects our changes from before. + command('wincmd p') screen:expect([[ - tty ready | - ^ | - |*4 + │^ | + │ |*4 + {18:[Scratch] }{17:[Scratch] }| {3:-- TERMINAL --} | ]]) + eq('horizontal', screen._mode_info[terminal_mode_idx].cursor_shape) + eq(0, screen._mode_info[terminal_mode_idx].blinkon) + eq(0, screen._mode_info[terminal_mode_idx].blinkoff) end) end) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index e2adcb66df..5e452ce9c9 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -215,7 +215,7 @@ describe('TUI', function() _G.termresponse = nil vim.api.nvim_create_autocmd('TermResponse', { once = true, - callback = function(ev) _G.termresponse = ev.data end, + callback = function(ev) _G.termresponse = ev.data.sequence end, }) ]]) feed_data('\027P0$r\027\\') @@ -2199,7 +2199,7 @@ describe('TUI', function() vim.api.nvim_create_autocmd('TermRequest', { buffer = buf, callback = function(args) - local req = args.data + local req = args.data.sequence if not req then return end @@ -2222,6 +2222,32 @@ describe('TUI', function() eq({ { id = 0xE1EA0000, url = 'https://example.com' } }, exec_lua([[return _G.urls]])) end) end) + + it('TermResponse works with vim.wait() from another autocommand #32706', function() + child_exec_lua([[ + _G.termresponse = nil + vim.api.nvim_create_autocmd('TermResponse', { + callback = function(ev) + _G.sequence = ev.data.sequence + _G.v_termresponse = vim.v.termresponse + end, + }) + vim.api.nvim_create_autocmd('InsertEnter', { + buffer = 0, + callback = function() + _G.result = vim.wait(3000, function() + local expected = '\027P1+r5463' + return _G.sequence == expected and _G.v_termresponse == expected + end) + end, + }) + ]]) + feed_data('i') + feed_data('\027P1+r5463\027\\') + retry(nil, 4000, function() + eq(true, child_exec_lua('return _G.result')) + end) + end) end) describe('TUI', function() @@ -3171,12 +3197,12 @@ describe('TUI', function() exec_lua([[ vim.api.nvim_create_autocmd('TermRequest', { callback = function(args) - local req = args.data - local payload = req:match('^\027P%+q([%x;]+)$') - if payload then + local req = args.data.sequence + local sequence = req:match('^\027P%+q([%x;]+)$') + if sequence then local t = {} - for cap in vim.gsplit(payload, ';') do - local resp = string.format('\027P1+r%s\027\\', payload) + for cap in vim.gsplit(sequence, ';') do + local resp = string.format('\027P1+r%s\027\\', sequence) vim.api.nvim_chan_send(vim.bo[args.buf].channel, resp) t[vim.text.hexdecode(cap)] = true end @@ -3222,7 +3248,7 @@ describe('TUI', function() exec_lua([[ vim.api.nvim_create_autocmd('TermRequest', { callback = function(args) - local req = args.data + local req = args.data.sequence vim.g.termrequest = req local xtgettcap = req:match('^\027P%+q([%x;]+)$') if xtgettcap then @@ -3274,10 +3300,10 @@ describe('TUI', function() exec_lua([[ vim.api.nvim_create_autocmd('TermRequest', { callback = function(args) - local req = args.data - local payload = req:match('^\027P%+q([%x;]+)$') - if payload and vim.text.hexdecode(payload) == 'Ms' then - local resp = string.format('\027P1+r%s=%s\027\\', payload, vim.text.hexencode('\027]52;;\027\\')) + local req = args.data.sequence + local sequence = req:match('^\027P%+q([%x;]+)$') + if sequence and vim.text.hexdecode(sequence) == 'Ms' then + local resp = string.format('\027P1+r%s=%s\027\\', sequence, vim.text.hexencode('\027]52;;\027\\')) vim.api.nvim_chan_send(vim.bo[args.buf].channel, resp) return true end @@ -3353,7 +3379,7 @@ describe('TUI bg color', function() exec_lua([[ vim.api.nvim_create_autocmd('TermRequest', { callback = function(args) - local req = args.data + local req = args.data.sequence if req == '\027]11;?' then vim.g.oscrequest = true return true diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index b7cf8504ff..58fd50e724 100644 --- a/test/functional/ui/bufhl_spec.lua +++ b/test/functional/ui/bufhl_spec.lua @@ -577,7 +577,7 @@ describe('Buffer highlighting', function() end) describe('virtual text decorations', function() - local id1, id2 + local id1, id2 ---@type integer, integer before_each(function() insert([[ 1 + 2 @@ -709,7 +709,7 @@ describe('Buffer highlighting', function() 0, 0, { - ns_id = 1, + ns_id = id1, priority = 0, virt_text = s1, -- other details @@ -729,7 +729,7 @@ describe('Buffer highlighting', function() lastline, 0, { - ns_id = 1, + ns_id = id1, priority = 0, virt_text = s2, -- other details @@ -898,11 +898,15 @@ describe('Buffer highlighting', function() end) it('and virtual text use the same namespace counter', function() - eq(1, add_highlight(0, 0, 'String', 0, 0, -1)) - eq(2, set_virtual_text(0, 0, 0, { { '= text', 'Comment' } }, {})) - eq(3, api.nvim_create_namespace('my-ns')) - eq(4, add_highlight(0, 0, 'String', 0, 0, -1)) - eq(5, set_virtual_text(0, 0, 0, { { '= text', 'Comment' } }, {})) - eq(6, api.nvim_create_namespace('other-ns')) + local base = vim.iter(api.nvim_get_namespaces()):fold(0, function(acc, _, v) + return math.max(acc, v) + end) + + eq(base + 1, add_highlight(0, 0, 'String', 0, 0, -1)) + eq(base + 2, set_virtual_text(0, 0, 0, { { '= text', 'Comment' } }, {})) + eq(base + 3, api.nvim_create_namespace('my-ns')) + eq(base + 4, add_highlight(0, 0, 'String', 0, 0, -1)) + eq(base + 5, set_virtual_text(0, 0, 0, { { '= text', 'Comment' } }, {})) + eq(base + 6, api.nvim_create_namespace('other-ns')) end) end) diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 0eb8391385..c0c0bf4fc1 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -16,7 +16,7 @@ local assert_alive = n.assert_alive local pcall_err = t.pcall_err describe('decorations providers', function() - local screen + local screen ---@type test.functional.ui.screen before_each(function() clear() screen = Screen.new(40, 8) @@ -52,6 +52,7 @@ describe('decorations providers', function() posp = getmark(mark, false); restore_buffer(&save_buf); ]] + --- @return integer local function setup_provider(code) return exec_lua ([[ local api = vim.api @@ -848,7 +849,8 @@ for _,item in ipairs(items) do end]] describe('extmark decorations', function() - local screen, ns + local screen ---@type test.functional.ui.screen + local ns ---@type integer before_each( function() clear() screen = Screen.new(50, 15) @@ -2232,7 +2234,7 @@ describe('extmark decorations', function() eq({ { 1, 0, 8, { end_col = 13, end_right_gravity = false, end_row = 0, hl_eol = false, hl_group = "NonText", undo_restore = false, - ns_id = 1, priority = 4096, right_gravity = true } } }, + ns_id = ns, priority = 4096, right_gravity = true } } }, api.nvim_buf_get_extmarks(0, ns, {0,0}, {0, -1}, {details=true})) end) @@ -2894,11 +2896,67 @@ describe('extmark decorations', function() {1:~ }|*4 | ]]) + -- Also with above virtual line #32744 + command('set nornu') + api.nvim_buf_set_extmark(0, ns, 3, 0, { virt_lines = { { { "virt_below 4" } } } }) + feed('6G') + screen:expect([[ + {2: 1 }for _,item in ipairs(items) do | + {2: 3 } if hl_id_cell ~= nil then | + {2: 4 } hl_id = hl_id_cell | + {2: }virt_below 4 | + {2: 6 } ^for _ = 1, (count or 1) do | + {2: 7 } local cell = line[colpos] | + {2: 8 } cell.text = text | + {2: 9 } cell.hl_id = hl_id | + {2: 10 } colpos = colpos+1 | + {2: 11 } end | + {2: 12 }end | + {1:~ }|*3 + | + ]]) + feed('j') + screen:expect([[ + {2: 1 }for _,item in ipairs(items) do | + {2: 3 } if hl_id_cell ~= nil then | + {2: 4 } hl_id = hl_id_cell | + {2: }virt_below 4 | + {2: 6 } for _ = 1, (count or 1) do | + {2: 7 } ^ local cell = line[colpos] | + {2: 8 } cell.text = text | + {2: 9 } cell.hl_id = hl_id | + {2: 10 } colpos = colpos+1 | + {2: 11 } end | + {2: 12 }end | + {1:~ }|*3 + | + ]]) + -- Even when virtual line is added as line is concealed #32762 + feed('5G') + api.nvim_buf_clear_namespace(0, ns, 3, 4) + feed('j') + api.nvim_buf_set_extmark(0, ns, 3, 0, { virt_lines = { { { "virt_below 4" } } } }) + screen:expect([[ + {2: 1 }for _,item in ipairs(items) do | + {2: 3 } if hl_id_cell ~= nil then | + {2: 4 } hl_id = hl_id_cell | + {2: }virt_below 4 | + {2: 6 }^ for _ = 1, (count or 1) do | + {2: 7 } local cell = line[colpos] | + {2: 8 } cell.text = text | + {2: 9 } cell.hl_id = hl_id | + {2: 10 } colpos = colpos+1 | + {2: 11 } end | + {2: 12 }end | + {1:~ }|*3 + | + ]]) end) end) describe('decorations: inline virtual text', function() - local screen, ns + local screen ---@type test.functional.ui.screen + local ns ---@type integer before_each( function() clear() screen = Screen.new(50, 3) @@ -4640,7 +4698,9 @@ describe('decorations: inline virtual text', function() end) describe('decorations: virtual lines', function() - local screen, ns + local screen ---@type test.functional.ui.screen + local ns ---@type integer + before_each(function() clear() screen = Screen.new(50, 12) @@ -5697,7 +5757,9 @@ if (h->n_buckets < new_n_buckets) { // expand end) describe('decorations: signs', function() - local screen, ns + local screen ---@type test.functional.ui.screen + local ns ---@type integer + before_each(function() clear() screen = Screen.new(50, 10) @@ -6307,7 +6369,7 @@ l5 end) describe('decorations: virt_text', function() - local screen + local screen ---@type test.functional.ui.screen before_each(function() clear() @@ -6381,7 +6443,10 @@ describe('decorations: virt_text', function() end) describe('decorations: window scoped', function() - local screen, ns, win_other + local screen ---@type test.functional.ui.screen + local ns ---@type integer + local win_other ---@type integer + local url = 'https://example.com' before_each(function() clear() diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index f6c9236037..6f3b38ce83 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -1851,15 +1851,16 @@ describe('builtin popupmenu', function() end) describe('floating window preview popup', function() - it('pum popup preview', function() + before_each(function() --row must > 10 screen:try_resize(40, 11) exec([[ + let g:list = [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "looooooooooooooong"}] funct Omni_test(findstart, base) if a:findstart return col(".") - 1 endif - return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "looooooooooooooong"}] + return g:list endfunc set omnifunc=Omni_test set completeopt=menu,popup @@ -1886,28 +1887,42 @@ describe('builtin popupmenu', function() autocmd! Group autocmd CompleteChanged * call TsHl() endfunc + funct Append_multipe() + call extend(g:list, [#{word: "for .. ipairs", info: "```lua\nfor index, value in ipairs(t) do\n\t\nend\n```"}]) + endfunc ]]) + end) + + it('pum popup preview', function() feed('Gi<C-x><C-o>') --floating preview in right if multigrid then - screen:expect { + screen:expect({ grid = [[ - ## grid 1 - [2:----------------------------------------]|*10 - [3:----------------------------------------]| - ## grid 2 - one^ | - {1:~ }|*9 - ## grid 3 - {2:-- }{5:match 1 of 3} | - ## grid 4 - {n:1info}| - {n: }| - ## grid 5 - {s:one }| - {n:two }| - {n:looooooooooooooong }| - ]], + ## grid 1 + [2:----------------------------------------]|*10 + [3:----------------------------------------]| + ## grid 2 + one^ | + {1:~ }|*9 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {n:1info}| + ## grid 5 + {s:one }| + {n:two }| + {n:looooooooooooooong }| + ]], + win_pos = { + [2] = { + height = 10, + startcol = 0, + startrow = 0, + width = 40, + win = 1000, + }, + }, float_pos = { [5] = { -1, 'NW', 2, 1, 0, false, 100 }, [4] = { 1001, 'NW', 1, 1, 19, false, 50 }, @@ -1925,25 +1940,39 @@ describe('builtin popupmenu', function() [4] = { win = 1001, topline = 0, - botline = 2, + botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0, }, }, - } + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000, + }, + [4] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1001, + }, + }, + }) else - screen:expect { - grid = [[ + screen:expect([[ one^ | {s:one }{n:1info}{1: }| - {n:two }{1: }| + {n:two }{1: }| {n:looooooooooooooong }{1: }| {1:~ }|*6 {2:-- }{5:match 1 of 3} | - ]], - } + ]]) end -- delete one character make the pum width smaller than before @@ -1962,10 +1991,18 @@ describe('builtin popupmenu', function() {2:-- }{5:match 1 of 3} | ## grid 4 {n:1info}| - {n: }| ## grid 5 {s:one }| ]], + win_pos = { + [2] = { + height = 10, + startcol = 0, + startrow = 0, + width = 40, + win = 1000, + }, + }, float_pos = { [5] = { -1, 'NW', 2, 1, 0, false, 100 }, [4] = { 1001, 'NW', 1, 1, 15, false, 50 }, @@ -1983,7 +2020,7 @@ describe('builtin popupmenu', function() [4] = { win = 1001, topline = 0, - botline = 2, + botline = 1, curline = 0, curcol = 0, linecount = 1, @@ -2008,15 +2045,12 @@ describe('builtin popupmenu', function() }, }) else - screen:expect({ - grid = [[ - on^ | - {s:one }{n:1info}{1: }| - {1:~ }{n: }{1: }| - {1:~ }|*7 - {2:-- }{5:match 1 of 3} | - ]], - }) + screen:expect([[ + on^ | + {s:one }{n:1info}{1: }| + {1:~ }|*8 + {2:-- }{5:match 1 of 3} | + ]]) end -- when back to original the preview float should be closed. @@ -2034,7 +2068,6 @@ describe('builtin popupmenu', function() {2:-- }{8:Back at original} | ## grid 4 (hidden) {n:1info}| - {n: }| ## grid 5 {n:one }| ]], @@ -2063,7 +2096,7 @@ describe('builtin popupmenu', function() [4] = { win = 1001, topline = 0, - botline = 2, + botline = 1, curline = 0, curcol = 0, linecount = 1, @@ -2097,9 +2130,11 @@ describe('builtin popupmenu', function() ]], }) end + feed('<C-E><ESC>') + end) - -- test nvim__complete_set_info - feed('<ESC>S<C-X><C-O><C-N><C-N>') + it('nvim__set_complete', function() + feed('S<C-X><C-O><C-N><C-N>') if multigrid then screen:expect({ grid = [[ @@ -2111,13 +2146,12 @@ describe('builtin popupmenu', function() {1:~ }|*9 ## grid 3 {2:-- }{5:match 3 of 3} | + ## grid 4 + {n:3info}| ## grid 5 {n:one }| {n:two }| {s:looooooooooooooong }| - ## grid 6 - {n:3info}| - {n: }| ]], win_pos = { [2] = { @@ -2130,7 +2164,7 @@ describe('builtin popupmenu', function() }, float_pos = { [5] = { -1, 'NW', 2, 1, 0, false, 100 }, - [6] = { 1002, 'NW', 1, 1, 19, false, 50 }, + [4] = { 1001, 'NW', 1, 1, 19, false, 50 }, }, win_viewport = { [2] = { @@ -2142,10 +2176,10 @@ describe('builtin popupmenu', function() linecount = 1, sum_scroll_delta = 0, }, - [6] = { - win = 1002, + [4] = { + win = 1001, topline = 0, - botline = 2, + botline = 1, curline = 0, curcol = 0, linecount = 1, @@ -2160,30 +2194,29 @@ describe('builtin popupmenu', function() top = 0, win = 1000, }, - [6] = { + [4] = { bottom = 0, left = 0, right = 0, top = 0, - win = 1002, + win = 1001, }, }, }) else - screen:expect { - grid = [[ + screen:expect([[ looooooooooooooong^ | {n:one 3info}{1: }| - {n:two }{1: }| + {n:two }{1: }| {s:looooooooooooooong }{1: }| {1:~ }|*6 {2:-- }{5:match 3 of 3} | - ]], - } + ]]) end + feed('<C-E><ESC>') + end) - -- preview in left - feed('<ESC>cc') + it('popup preview place in left', function() insert(('test'):rep(5)) feed('i<C-x><C-o>') if multigrid then @@ -2193,17 +2226,16 @@ describe('builtin popupmenu', function() [2:----------------------------------------]|*10 [3:----------------------------------------]| ## grid 2 - itesttesttesttesttesone^t | + testtesttesttesttesone^t | {1:~ }|*9 ## grid 3 {2:-- }{5:match 1 of 3} | + ## grid 4 + {n:1info}| ## grid 5 {s: one }| {n: two }| {n: looooooooooooooong }| - ## grid 7 - {n:1info}| - {n: }| ]], win_pos = { [2] = { @@ -2215,8 +2247,8 @@ describe('builtin popupmenu', function() }, }, float_pos = { - [7] = { 1003, 'NW', 1, 1, 14, false, 50 }, - [5] = { -1, 'NW', 2, 1, 19, false, 100 }, + [5] = { -1, 'NW', 2, 1, 18, false, 100 }, + [4] = { 1001, 'NW', 1, 1, 13, false, 50 }, }, win_viewport = { [2] = { @@ -2224,14 +2256,14 @@ describe('builtin popupmenu', function() topline = 0, botline = 2, curline = 0, - curcol = 23, + curcol = 22, linecount = 1, sum_scroll_delta = 0, }, - [7] = { - win = 1003, + [4] = { + win = 1001, topline = 0, - botline = 2, + botline = 1, curline = 0, curcol = 0, linecount = 1, @@ -2246,30 +2278,29 @@ describe('builtin popupmenu', function() top = 0, win = 1000, }, - [7] = { + [4] = { bottom = 0, left = 0, right = 0, top = 0, - win = 1003, + win = 1001, }, }, }) else - screen:expect { - grid = [[ - itesttesttesttesttesone^t | - {1:~ }{n:1info}{s: one }{1: }| - {1:~ }{n: two }{1: }| - {1:~ }{n: looooooooooooooong }{1: }| + screen:expect([[ + testtesttesttesttesone^t | + {1:~ }{n:1info}{s: one }{1: }| + {1:~ }{n: two }{1: }| + {1:~ }{n: looooooooooooooong }{1: }| {1:~ }|*6 {2:-- }{5:match 1 of 3} | - ]], - } + ]]) end feed('<C-E><Esc>') + end) - -- works when scroll with treesitter highlight + it('works when scroll with treesitter highlight', function() command('call TestTs()') feed('S<C-x><C-o>') if multigrid then @@ -2283,17 +2314,16 @@ describe('builtin popupmenu', function() {1:~ }|*9 ## grid 3 {2:-- }{5:match 1 of 3} | - ## grid 5 - {s:one }| - {n:two }| - {n:looooooooooooooong }| - ## grid 8 + ## grid 4 {n:```lua }| {n:function test()}| {n: print('foo') }| {n:end }| {n:``` }| - {n: }| + ## grid 5 + {s:one }| + {n:two }| + {n:looooooooooooooong }| ]], win_pos = { [2] = { @@ -2306,7 +2336,7 @@ describe('builtin popupmenu', function() }, float_pos = { [5] = { -1, 'NW', 2, 1, 0, false, 100 }, - [8] = { 1004, 'NW', 1, 1, 19, false, 50 }, + [4] = { 1001, 'NW', 1, 1, 19, false, 50 }, }, win_viewport = { [2] = { @@ -2318,10 +2348,10 @@ describe('builtin popupmenu', function() linecount = 1, sum_scroll_delta = 0, }, - [8] = { - win = 1004, + [4] = { + win = 1001, topline = 0, - botline = 6, + botline = 5, curline = 0, curcol = 0, linecount = 5, @@ -2336,30 +2366,127 @@ describe('builtin popupmenu', function() top = 0, win = 1000, }, - [8] = { + [4] = { bottom = 0, left = 0, right = 0, top = 0, - win = 1004, + win = 1001, }, }, }) else + screen:expect([[ + one^ | + {s:one }{n:```lua }{1: }| + {n:two function test()}{1: }| + {n:looooooooooooooong print('foo') }{1: }| + {1:~ }{n:end }{1: }| + {1:~ }{n:``` }{1: }| + {1:~ }|*4 + {2:-- }{5:match 1 of 3} | + ]]) + end + feed('<C-E><ESC>') + end) + + it('#avoid modified original info text', function() + screen:try_resize(40, 11) + command('call Append_multipe()') + feed('S<C-x><C-o><C-P><C-P>') + if multigrid then screen:expect({ grid = [[ - one^ | - {s:one }{n:```lua }{1: }| - {n:two function test()}{1: }| - {n:looooooooooooooong print('foo') }{1: }| - {1:~ }{n:end }{1: }| - {1:~ }{n:``` }{1: }| - {1:~ }{n: }{1: }| - {1:~ }|*3 - {2:-- }{5:match 1 of 3} | + ## grid 1 + [2:----------------------------------------]|*10 + [3:----------------------------------------]| + ## grid 2 + for .. ipairs^ | + {1:~ }|*9 + ## grid 3 + {2:-- }{5:match 1 of 4} | + ## grid 4 + {n:one }| + {n:two }| + {n:looooooooooooooong }| + {s:for .. ipairs }| + ## grid 5 + {n:```lua }| + {n:for index, value in }| + {n:ipairs(t) do }| + {n: }| + {n:end }| + {n:``` }| ]], + win_pos = { + [2] = { + height = 10, + startcol = 0, + startrow = 0, + width = 40, + win = 1000, + }, + }, + float_pos = { + [5] = { 1001, 'NW', 1, 1, 19, false, 50 }, + [4] = { -1, 'NW', 2, 1, 0, false, 100 }, + }, + win_viewport = { + [2] = { + win = 1000, + topline = 0, + botline = 2, + curline = 0, + curcol = 13, + linecount = 1, + sum_scroll_delta = 0, + }, + [5] = { + win = 1001, + topline = 0, + botline = 5, + curline = 0, + curcol = 0, + linecount = 5, + sum_scroll_delta = 0, + }, + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000, + }, + [5] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1001, + }, + }, }) + else + screen:expect([[ + for .. ipairs^ | + {n:one ```lua }{1: }| + {n:two for index, value in }{1: }| + {n:looooooooooooooong ipairs(t) do }{1: }| + {s:for .. ipairs }{n: }{1: }| + {1:~ }{n:end }{1: }| + {1:~ }{n:``` }{1: }| + {1:~ }|*3 + {2:-- }{5:match 1 of 4} | + ]]) end + + feed('<C-N><C-N><C-N><C-N><C-N>') + if not multigrid then + screen:expect_unchanged() + end + feed('<C-E><ESC>') end) end) @@ -5145,6 +5272,29 @@ describe('builtin popupmenu', function() ]]) end) + -- oldtest: Test_wildmenu_pum_hl_nonfirst() + it('highlight matched text in the middle in cmdline pum', function() + exec([[ + set wildoptions=pum wildchar=<tab> wildmode=noselect,full + hi PmenuMatchSel guifg=Blue guibg=Grey + hi PmenuMatch guifg=Blue guibg=Plum1 + func T(a, c, p) + return ["oneA", "o neBneB", "aoneC"] + endfunc + command -nargs=1 -complete=customlist,T MyCmd + ]]) + + feed(':MyCmd ne<tab>') + screen:expect([[ + | + {1:~ }|*15 + {1:~ }{n: o}{mn:ne}{n:A }{1: }| + {1:~ }{n: o }{mn:ne}{n:BneB }{1: }| + {1:~ }{n: ao}{mn:ne}{n:C }{1: }| + :MyCmd ne^ | + ]]) + end) + it( 'cascading highlights for matched text (PmenuMatch, PmenuMatchSel) in cmdline pum', function() @@ -7287,6 +7437,7 @@ describe('builtin popupmenu', function() return [#{word: "func ()\n\t\nend", abbr: "function ()",}, #{word: "foobar"}, #{word: "你好\n\t\n我好"}] endfunc set omnifunc=Omni_test + inoremap <F5> <Cmd>call complete(col('.'), [ "my\n\tmulti\nline", "my\n\t\tmulti\nline" ])<CR> ]]) feed('S<C-X><C-O>') @@ -7429,6 +7580,21 @@ describe('builtin popupmenu', function() {1:~ }|*14 {2:-- INSERT --} | ]]) + + feed('<Esc>ggVGd') + command('filetype indent on') + command('setlocal noautoindent shiftwidth& tabstop&') + command('setlocal ft=lua') + feed('S<F5>') + screen:expect([[ + {8:my} | + {8: multi} | + {8:line}^ | + {s:my^@ multi^@line }{1: }| + {n:my^@ multi^@line }{1: }| + {1:~ }|*14 + {2:-- INSERT --} | + ]]) end) end end diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim index 64876ce318..d1d27cf578 100644 --- a/test/old/testdir/gen_opt_test.vim +++ b/test/old/testdir/gen_opt_test.vim @@ -352,6 +352,7 @@ let test_values = { \ 'bs'], \ ['xxx']], \ 'wildmode': [['', 'full', 'longest', 'list', 'lastused', 'list:full', + \ 'noselect', 'noselect,full', 'noselect:lastused,full', \ 'full,longest', 'full,full,full,full'], \ ['xxx', 'a4', 'full,full,full,full,full']], \ 'wildoptions': [['', 'tagfile', 'pum', 'fuzzy'], ['xxx']], diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index d4ad63d43e..639a4c1d32 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -2168,22 +2168,58 @@ func Wildmode_tests() call assert_equal('AAA AAAA AAAAA', g:Sline) call assert_equal('"b A', @:) + " When 'wildmenu' is not set, 'noselect' completes first item + set wildmode=noselect + call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd oneA', @:) + + " When 'noselect' is present, do not complete first <tab>. + set wildmenu + set wildmode=noselect + call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd o', @:) + call feedkeys(":MyCmd o\t\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd o', @:) + call feedkeys(":MyCmd o\t\t\<C-Y>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd o', @:) + + " When 'full' is present, complete after first <tab>. + set wildmode=noselect,full + call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd o', @:) + call feedkeys(":MyCmd o\t\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd oneA', @:) + call feedkeys(":MyCmd o\t\t\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd oneB', @:) + call feedkeys(":MyCmd o\t\t\t\<C-Y>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd oneB', @:) + + " 'noselect' has no effect when 'longest' is present. + set wildmode=noselect:longest + call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd one', @:) + + " Complete 'noselect' value in 'wildmode' option + set wildmode& + call feedkeys(":set wildmode=n\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"set wildmode=noselect', @:) + call feedkeys(":set wildmode=\t\t\t\t\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"set wildmode=noselect', @:) + " when using longest completion match, matches shorter than the argument " should be ignored (happens with :help) set wildmode=longest,full - set wildmenu call feedkeys(":help a*\t\<C-B>\"\<CR>", 'xt') call assert_equal('"help a', @:) " non existing file call feedkeys(":e a1b2y3z4\t\<C-B>\"\<CR>", 'xt') call assert_equal('"e a1b2y3z4', @:) - set wildmenu& " Test for longest file name completion with 'fileignorecase' " On MS-Windows, file names are case insensitive. if has('unix') - call writefile([], 'XTESTfoo') - call writefile([], 'Xtestbar') + call writefile([], 'XTESTfoo', 'D') + call writefile([], 'Xtestbar', 'D') set nofileignorecase call feedkeys(":e XT\<Tab>\<C-B>\"\<CR>", 'xt') call assert_equal('"e XTESTfoo', @:) @@ -2195,10 +2231,23 @@ func Wildmode_tests() call feedkeys(":e Xt\<Tab>\<C-B>\"\<CR>", 'xt') call assert_equal('"e Xtest', @:) set fileignorecase& - call delete('XTESTfoo') - call delete('Xtestbar') endif + " If 'noselect' is present, single item menu should not insert item + func! T(a, c, p) + return "oneA" + endfunc + command! -nargs=1 -complete=custom,T MyCmd + set wildmode=noselect,full + call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd o', @:) + call feedkeys(":MyCmd o\t\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd oneA', @:) + " 'nowildmenu' should make 'noselect' ineffective + set nowildmenu + call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd oneA', @:) + %argdelete delcommand MyCmd delfunc T @@ -2976,6 +3025,28 @@ func Test_wildmenu_pum_rightleft() call StopVimInTerminal(buf) endfunc +" Test highlighting when pattern matches non-first character of item +func Test_wildmenu_pum_hl_nonfirst() + CheckScreendump + let lines =<< trim END + set wildoptions=pum wildchar=<tab> wildmode=noselect,full + hi PmenuMatchSel ctermfg=6 ctermbg=7 + hi PmenuMatch ctermfg=4 ctermbg=225 + func T(a, c, p) + return ["oneA", "o neBneB", "aoneC"] + endfunc + command -nargs=1 -complete=customlist,T MyCmd + END + + call writefile(lines, 'Xwildmenu_pum_hl_nonf', 'D') + let buf = RunVimInTerminal('-S Xwildmenu_pum_hl_nonf', #{rows: 10, cols: 50}) + + call term_sendkeys(buf, ":MyCmd ne\<tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_hl_match_nonf', {}) + call term_sendkeys(buf, "\<Esc>") + call StopVimInTerminal(buf) +endfunc + " Test highlighting matched text in cmdline completion popup menu. func Test_wildmenu_pum_hl_match() CheckScreendump diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 37337d9b54..296062f92c 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -88,6 +88,7 @@ func s:GetFilenameChecks() abort \ 'abap': ['file.abap'], \ 'abc': ['file.abc'], \ 'abel': ['file.abl'], + \ 'abnf': ['file.abnf'], \ 'acedb': ['file.wrm'], \ 'ada': ['file.adb', 'file.ads', 'file.ada', 'file.gpr'], \ 'ahdl': ['file.tdf'], @@ -800,6 +801,7 @@ func s:GetFilenameChecks() abort \ 'teal': ['file.tl'], \ 'templ': ['file.templ'], \ 'template': ['file.tmpl'], + \ 'tera': ['file.tera', 'file.toml.tera', 'file.html.tera', 'file.css.tera'], \ 'teraterm': ['file.ttl'], \ 'terminfo': ['file.ti'], \ 'terraform-vars': ['file.tfvars'], diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index d339bbe4e1..c2062a355f 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -1208,8 +1208,8 @@ func Test_complete_cmdline() call assert_equal('abcxyz(', getline(3)) com! -buffer TestCommand1 echo 'TestCommand1' com! -buffer TestCommand2 echo 'TestCommand2' - write TestCommand1Test - write TestCommand2Test + write! TestCommand1Test + write! TestCommand2Test " Test repeating <CTRL-X> <CTRL-V> and switching to another CTRL-X mode exe "normal oT\<C-X>\<C-V>\<C-X>\<C-V>\<C-X>\<C-F>\<Esc>" call assert_equal('TestCommand2Test', getline(4)) diff --git a/test/old/testdir/test_plugin_matchparen.vim b/test/old/testdir/test_plugin_matchparen.vim index 7d80e43046..0739906a42 100644 --- a/test/old/testdir/test_plugin_matchparen.vim +++ b/test/old/testdir/test_plugin_matchparen.vim @@ -139,4 +139,36 @@ func Test_matchparen_mbyte() call StopVimInTerminal(buf) endfunc +" Test for ignoring certain parenthesis +func Test_matchparen_ignore_sh_case() + CheckScreendump + + let lines =<< trim END + source $VIMRUNTIME/plugin/matchparen.vim + set ft=sh + call setline(1, [ + \ '#!/bin/sh', + \ 'SUSUWU_PRINT() (', + \ ' case "${LEVEL}" in', + \ ' "$SUSUWU_SH_NOTICE")', + \ ' ${SUSUWU_S} && return 1', + \ ' ;;', + \ ' "$SUSUWU_SH_DEBUG")', + \ ' (! ${SUSUWU_VERBOSE}) && return 1', + \ ' ;;', + \ ' esac', + \ ' # snip', + \ ')' + \ ]) + call cursor(4, 26) + END + + let filename = 'Xmatchparen_sh' + call writefile(lines, filename, 'D') + + let buf = RunVimInTerminal('-S '.filename, #{rows: 10}) + call VerifyScreenDump(buf, 'Test_matchparen_sh_case_1', {}) + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index 0b88fd1dba..988d60916c 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -1896,6 +1896,7 @@ func Test_pum_complete_with_special_characters() return [#{word: "func ()\n\t\nend", abbr: "function ()",}, #{word: "foobar"}, #{word: "你好\n\t\n我好"}] endfunc set omnifunc=Omni_test + inoremap <F5> <Cmd>call complete(col('.'), [ "my\n\tmulti\nline", "my\n\t\tmulti\nline" ])<CR> END call writefile(lines, 'Xpreviewscript', 'D') @@ -1954,6 +1955,14 @@ func Test_pum_complete_with_special_characters() call TermWait(buf, 50) call VerifyScreenDump(buf, 'Test_pum_with_special_characters_12', {}) + call term_sendkeys(buf, "\<ESC>ggVGd") + call term_sendkeys(buf, ":filetype indent on\<CR>") + call term_sendkeys(buf, ":set nocompatible autoindent& shiftwidth& tabstop&\<CR>") + call term_sendkeys(buf, ":setlocal ft=lua\<CR>") + call term_sendkeys(buf, "S\<F5>") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_with_special_characters_13', {}) + call StopVimInTerminal(buf) endfunc diff --git a/test/old/testdir/test_registers.vim b/test/old/testdir/test_registers.vim index 2557f2bcbd..02da2ac689 100644 --- a/test/old/testdir/test_registers.vim +++ b/test/old/testdir/test_registers.vim @@ -1037,4 +1037,54 @@ func Test_register_cursor_column_negative() call StopVimInTerminal(buf) endfunc +" test '] mark generated by op_yank +func Test_mark_from_yank() + new + " double quote object + call setline(1, 'test "yank" mark') + normal! yi" + call assert_equal([0, 1, 10, 0], getpos("']")) + normal! ya" + call assert_equal([0, 1, 13, 0], getpos("']")) + " single quote object + call setline(1, 'test ''yank'' mark') + normal! yi' + call assert_equal([0, 1, 10, 0], getpos("']")) + normal! ya' + call assert_equal([0, 1, 13, 0], getpos("']")) + " paren object + call setline(1, 'test (yank) mark') + call cursor(1, 9) + normal! yi( + call assert_equal([0, 1, 10, 0], getpos("']")) + call cursor(1, 9) + normal! ya( + call assert_equal([0, 1, 11, 0], getpos("']")) + " brace object + call setline(1, 'test {yank} mark') + call cursor(1, 9) + normal! yi{ + call assert_equal([0, 1, 10, 0], getpos("']")) + call cursor(1, 9) + normal! ya{ + call assert_equal([0, 1, 11, 0], getpos("']")) + " bracket object + call setline(1, 'test [yank] mark') + call cursor(1, 9) + normal! yi[ + call assert_equal([0, 1, 10, 0], getpos("']")) + call cursor(1, 9) + normal! ya[ + call assert_equal([0, 1, 11, 0], getpos("']")) + " block object + call setline(1, 'test <yank> mark') + call cursor(1, 9) + normal! yi< + call assert_equal([0, 1, 10, 0], getpos("']")) + call cursor(1, 9) + normal! ya< + call assert_equal([0, 1, 11, 0], getpos("']")) + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_tagjump.vim b/test/old/testdir/test_tagjump.vim index efc5e4cebe..74ae4a6c73 100644 --- a/test/old/testdir/test_tagjump.vim +++ b/test/old/testdir/test_tagjump.vim @@ -1696,4 +1696,21 @@ func Test_tag_guess_short() set tags& cpoptions-=t endfunc +func Test_tag_excmd_with_nostartofline() + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "f\tXfile\tascii"], + \ 'Xtags', 'D') + call writefile(['f', 'foobar'], 'Xfile', 'D') + + set nostartofline + new Xfile + setlocal tags=Xtags + normal! G$ + " This used to cause heap-buffer-overflow + tag f + + bwipe! + set startofline& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_visual.vim b/test/old/testdir/test_visual.vim index 328ac502bf..f5a5970e24 100644 --- a/test/old/testdir/test_visual.vim +++ b/test/old/testdir/test_visual.vim @@ -1101,6 +1101,50 @@ func Test_exclusive_selection() bw! endfunc +" Test for inclusive motion in visual mode with 'exclusive' selection +func Test_inclusive_motion_selection_exclusive() + func s:compare_exclu_inclu(line, keys, expected_exclu) + let msg = "data: '" . a:line . "' operation: '" . a:keys . "'" + call setline(1, a:line) + set selection=exclusive + call feedkeys(a:keys, 'xt') + call assert_equal(a:expected_exclu, getpos('.'), msg) + let pos_ex = col('.') + set selection=inclusive + call feedkeys(a:keys, 'xt') + let pos_in = col('.') + call assert_equal(1, pos_ex - pos_in, msg) + endfunc + + new + " Test 'e' motion + set selection=exclusive + call setline(1, 'eins zwei drei') + norm! ggvey + call assert_equal('eins', @") + call setline(1, 'abc(abc)abc') + norm! ggveeed + call assert_equal(')abc', getline(1)) + call setline(1, 'abc(abc)abc') + norm! gg3lvey + call assert_equal('(abc', @") + call s:compare_exclu_inclu('abc(abc)abc', 'ggveee', [0, 1, 8, 0]) + " Test 'f' motion + call s:compare_exclu_inclu('geschwindigkeit', 'ggvfefe', [0, 1, 14, 0]) + call s:compare_exclu_inclu('loooooooooooong', 'ggv2fo2fo2fo', [0, 1, 8, 0]) + " Test 't' motion + call s:compare_exclu_inclu('geschwindigkeit', 'ggv2te', [0, 1, 13, 0]) + call s:compare_exclu_inclu('loooooooooooong', 'gglv2to2to2to', [0, 1, 6, 0]) + " Test ';' motion + call s:compare_exclu_inclu('geschwindigkeit', 'ggvfi;;', [0, 1, 15, 0]) + call s:compare_exclu_inclu('geschwindigkeit', 'ggvti;;', [0, 1, 14, 0]) + call s:compare_exclu_inclu('loooooooooooong', 'ggv2fo;;', [0, 1, 6, 0]) + call s:compare_exclu_inclu('loooooooooooong', 'ggvl2to;;', [0, 1, 6, 0]) + " Clean up + set selection& + bw! +endfunc + " Test for starting linewise visual with a count. " This test needs to be run without any previous visual mode. Otherwise the " count will use the count from the previous visual mode. diff --git a/test/unit/multiqueue_spec.lua b/test/unit/multiqueue_spec.lua index 931a5efa94..36dae403be 100644 --- a/test/unit/multiqueue_spec.lua +++ b/test/unit/multiqueue_spec.lua @@ -25,7 +25,7 @@ describe('multiqueue (multi-level event-queue)', function() before_each(function() child_call_once(function() - parent = multiqueue.multiqueue_new_parent(ffi.NULL, ffi.NULL) + parent = multiqueue.multiqueue_new(ffi.NULL, ffi.NULL) child1 = multiqueue.multiqueue_new_child(parent) child2 = multiqueue.multiqueue_new_child(parent) child3 = multiqueue.multiqueue_new_child(parent) |