diff options
94 files changed, 3598 insertions, 958 deletions
diff --git a/.gitattributes b/.gitattributes index 381fc27e80..381fc27e80 100755..100644 --- a/.gitattributes +++ b/.gitattributes diff --git a/.gitignore b/.gitignore index 98ec98ea72..b36b55cef5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ compile_commands.json /.idea/ # Build/deps dir -/build/ /.deps/ /tmp/ /.clangd/ diff --git a/CMakeLists.txt b/CMakeLists.txt index e129901f92..01462ab233 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,11 @@ include(InstallHelpers) include(PreventInTreeBuilds) include(Util) +if(NOT PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) + # Auto-create a .gitignore in the specified "build" directory. + file(GENERATE OUTPUT .gitignore CONTENT "*") +endif() + #------------------------------------------------------------------------------- # User settings #------------------------------------------------------------------------------- diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index abbd41d62a..7e7feb50e0 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -5,11 +5,11 @@ if exists('g:loaded_clipboard_provider') finish endif -" Default to 1. provider#clipboard#Executable() may set 2. +" Default to 0. provider#clipboard#Executable() may set 2. " To force a reload: " :unlet g:loaded_clipboard_provider " :runtime autoload/provider/clipboard.vim -let g:loaded_clipboard_provider = 1 +let g:loaded_clipboard_provider = 0 let s:copy = {} let s:paste = {} @@ -284,4 +284,4 @@ function! provider#clipboard#Call(method, args) abort endfunction " eval_has_provider() decides based on this variable. -let g:loaded_clipboard_provider = empty(provider#clipboard#Executable()) ? 1 : 2 +let g:loaded_clipboard_provider = empty(provider#clipboard#Executable()) ? 0 : 2 diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index 3e7b8b4ef9..7cd5cab864 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -1,7 +1,7 @@ if exists('g:loaded_node_provider') finish endif -let g:loaded_node_provider = 1 +let g:loaded_node_provider = 0 function! s:is_minimum_version(version, min_version) abort if empty(a:version) @@ -152,7 +152,7 @@ endfunction let s:err = '' let [s:prog, s:_] = provider#node#Detect() -let g:loaded_node_provider = empty(s:prog) ? 1 : 2 +let g:loaded_node_provider = empty(s:prog) ? 0 : 2 if g:loaded_node_provider != 2 let s:err = 'Cannot find the "neovim" node package. Try :checkhealth' diff --git a/runtime/autoload/provider/perl.vim b/runtime/autoload/provider/perl.vim index b439f751ae..5ebcc28aaf 100644 --- a/runtime/autoload/provider/perl.vim +++ b/runtime/autoload/provider/perl.vim @@ -11,5 +11,5 @@ function! provider#perl#Require(host) abort endfunction let s:prog = v:lua.vim.provider.perl.detect() -let g:loaded_perl_provider = empty(s:prog) ? 1 : 2 +let g:loaded_perl_provider = empty(s:prog) ? 0 : 2 call v:lua.require'vim.provider.perl'.start() diff --git a/runtime/autoload/provider/python3.vim b/runtime/autoload/provider/python3.vim index 43c14122d0..8c8a466e0d 100644 --- a/runtime/autoload/provider/python3.vim +++ b/runtime/autoload/provider/python3.vim @@ -11,5 +11,5 @@ function! provider#python3#Require(host) abort endfunction let s:prog = v:lua.vim.provider.python.detect_by_module('neovim') -let g:loaded_python3_provider = empty(s:prog) ? 1 : 2 +let g:loaded_python3_provider = empty(s:prog) ? 0 : 2 call v:lua.require'vim.provider.python'.start() diff --git a/runtime/autoload/provider/ruby.vim b/runtime/autoload/provider/ruby.vim index 07b4aca23f..9bcbcfc9dd 100644 --- a/runtime/autoload/provider/ruby.vim +++ b/runtime/autoload/provider/ruby.vim @@ -11,6 +11,6 @@ function! provider#ruby#Call(method, args) abort endfunction let s:prog = v:lua.vim.provider.ruby.detect() -let g:loaded_ruby_provider = empty(s:prog) ? 1 : 2 +let g:loaded_ruby_provider = empty(s:prog) ? 0 : 2 let s:plugin_path = expand('<sfile>:p:h') . '/script_host.rb' call v:lua.require'vim.provider.ruby'.start(s:plugin_path) diff --git a/runtime/colors/vim.lua b/runtime/colors/vim.lua index f10cdedee0..1b6ab01671 100644 --- a/runtime/colors/vim.lua +++ b/runtime/colors/vim.lua @@ -47,6 +47,7 @@ hi('WildMenu', { fg = 'Black', bg = 'Yellow', ctermfg = 'Black', cterm hi('VertSplit', { link = 'Normal' }) hi('WinSeparator', { link = 'VertSplit' }) hi('WinBarNC', { link = 'WinBar' }) +hi('DiffTextAdd', { link = 'DiffText' }) hi('EndOfBuffer', { link = 'NonText' }) hi('LineNrAbove', { link = 'LineNr' }) hi('LineNrBelow', { link = 'LineNr' }) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index eba4bc9562..ccfc117040 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3492,40 +3492,39 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()* empty. The end-of-buffer region is hidden by setting `eob` flag of 'fillchars' to a space char, and clearing the |hl-EndOfBuffer| region in 'winhighlight'. - • border: Style of (optional) window border. This can either - be a string or an array. The string values are the same as - those described in 'winborder'. If it is an array, it - should have a length of eight or any divisor of eight. The - array will specify the eight chars building up the border - in a clockwise fashion starting with the top-left corner. - As an example, the double box style could be specified as: > - [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. + • border: (`string|string[]`) (defaults to 'winborder' + option) Window border. The string form accepts the same + values as the 'winborder' option. The array form must have + a length of eight or any divisor of eight, specifying the + chars that form the border in a clockwise fashion starting + from the top-left corner. For example, the double-box + style can be specified as: > + [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. < - If the number of chars are less than eight, they will be - repeated. Thus an ASCII border could be specified as > - [ "/", "-", \"\\\\\", "|" ], + If fewer than eight chars are given, they will be + repeated. An ASCII border could be specified as: > + [ "/", "-", \"\\\\\", "|" ], < - or all chars the same as > - [ "x" ]. + Or one char for all sides: > + [ "x" ]. < - An empty string can be used to turn off a specific border, - for instance, > - [ "", "", "", ">", "", "", "", "<" ] + Empty string can be used to hide a specific border. This + example will show only vertical borders, not horizontal: > + [ "", "", "", ">", "", "", "", "<" ] < - will only make vertical borders but not horizontal ones. - By default, `FloatBorder` highlight is used, which links - to `WinSeparator` when not defined. It could also be - specified by character: > - [ ["+", "MyCorner"], ["x", "MyBorder"] ]. + By default, |hl-FloatBorder| highlight is used, which + links to |hl-WinSeparator| when not defined. Each border + side can specify an optional highlight: > + [ ["+", "MyCorner"], ["x", "MyBorder"] ]. < - • title: Title (optional) in window border, string or list. + • title: (optional) Title in window border, string or list. List should consist of `[text, highlight]` tuples. If string, or a tuple lacks a highlight, the default highlight group is `FloatTitle`. • title_pos: Title position. Must be set with `title` option. Value can be one of "left", "center", or "right". Default is `"left"`. - • footer: Footer (optional) in window border, string or + • footer: (optional) Footer in window border, string or list. List should consist of `[text, highlight]` tuples. If string, or a tuple lacks a highlight, the default highlight group is `FloatFooter`. @@ -3773,8 +3772,8 @@ nvim_create_autocmd({event}, {opts}) *nvim_create_autocmd()* • callback (function|string) optional: Lua function (or Vimscript function name, if string) called when the event(s) is triggered. Lua callback can return a truthy - value (not `false` or `nil`) to delete the autocommand. - Receives one argument, a table with these keys: + value (not `false` or `nil`) to delete the autocommand, and + receives one argument, a table with these keys: *event-args* • id: (number) autocommand id • event: (string) name of the triggered event diff --git a/runtime/doc/diff.txt b/runtime/doc/diff.txt index c9de54342e..f39a51022a 100644 --- a/runtime/doc/diff.txt +++ b/runtime/doc/diff.txt @@ -214,14 +214,28 @@ The diffs are highlighted with these groups: |hl-DiffAdd| DiffAdd Added (inserted) lines. These lines exist in this buffer but not in another. |hl-DiffChange| DiffChange Changed lines. -|hl-DiffText| DiffText Changed text inside a Changed line. Vim - finds the first character that is different, - and the last character that is different - (searching from the end of the line). The - text in between is highlighted. This means - that parts in the middle that are still the - same are highlighted anyway. The 'diffopt' - flags "iwhite" and "icase" are used here. +|hl-DiffText| DiffText Changed text inside a Changed line. Exact + behavior depends on the `inline:` setting in + 'diffopt'. + With `inline:` set to "simple", Vim finds the + first character that is different, and the + last character that is different (searching + from the end of the line). The text in + between is highlighted. This means that parts + in the middle that are still the same are + highlighted anyway. The 'diffopt' flags + "iwhite" and "icase" are used here. + With `inline:` set to "char" or "word", Vim + uses the internal diff library to perform a + detailed diff between the changed blocks and + highlight the exact difference between the + two. Will respect any 'diffopt' flag that + affects internal diff. + Not used when `inline:` is set to "none". +|hl-DiffTextAdd| DiffTextAdd Added text inside a Changed line. Similar to + DiffText, but used when there is no + corresponding text in other buffers. Not used + when `inline:` is set to "simple" or "none". |hl-DiffDelete| DiffDelete Deleted lines. Also called filler lines, because they don't really exist in this buffer. diff --git a/runtime/doc/faq.txt b/runtime/doc/faq.txt index a9a7b1768e..b41a99a3f8 100644 --- a/runtime/doc/faq.txt +++ b/runtime/doc/faq.txt @@ -205,17 +205,11 @@ Other hints: :CHECKHEALTH REPORTS E5009: INVALID $VIMRUNTIME ~ -This means `health#check()` couldn't load, which suggests that |$VIMRUNTIME| -or 'runtimepath' is broken. +This means |$VIMRUNTIME| or 'runtimepath' is broken. - |$VIMRUNTIME| must point to Nvim's runtime files, not Vim's. - The |$VIMRUNTIME| directory contents should be readable by the current user. - Verify that `:echo &runtimepath` contains the $VIMRUNTIME path. -- Check the output of: >vim - - :call health#check() - :verbose func health#check -< NEOVIM CAN'T FIND ITS RUNTIME ~ diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index 3c8bb0814a..95980faac8 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -651,10 +651,11 @@ When completion is active you can use CTRL-E to stop it and go back to the originally typed text. The CTRL-E will not be inserted. *complete_CTRL-Y* -When the popup menu is displayed you can use CTRL-Y to stop completion and -accept the currently selected entry. The CTRL-Y is not inserted. Typing a -space, Enter, or some other unprintable character will leave completion mode -and insert that typed character. +When the popup menu is displayed, CTRL-Y stops completion and accepts the +currently selected entry. Typing a space, Enter, or some other unprintable +character will leave completion mode and insert that typed character. If you +want to use <Enter> to accept a completion item, use this mapping: >vim + inoremap <expr> <cr> pumvisible() ? '<c-y>' : '<cr>' When the popup menu is displayed there are a few more special keys, see |popupmenu-keys|. diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 6d232657c9..da7f40325a 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -76,6 +76,7 @@ listed below, if (1) the language server supports the functionality and (2) the options are empty or were set by the builtin runtime (ftplugin) files. The options are not restored when the LSP client is stopped or detached. +BUFFER-LOCAL DEFAULTS - 'omnifunc' is set to |vim.lsp.omnifunc()|, use |i_CTRL-X_CTRL-O| to trigger completion. - 'tagfunc' is set to |vim.lsp.tagfunc()|. This enables features like @@ -87,8 +88,9 @@ options are not restored when the LSP client is stopped or detached. - |K| is mapped to |vim.lsp.buf.hover()| unless |'keywordprg'| is customized or a custom keymap for `K` exists. +GLOBAL DEFAULTS *grr* *gra* *grn* *gri* *i_CTRL-S* -Some keymaps are created unconditionally when Nvim starts: +These GLOBAL keymaps are created unconditionally when Nvim starts: - "grn" is mapped in Normal mode to |vim.lsp.buf.rename()| - "gra" is mapped in Normal and Visual mode to |vim.lsp.buf.code_action()| - "grr" is mapped in Normal mode to |vim.lsp.buf.references()| @@ -96,12 +98,11 @@ Some keymaps are created unconditionally when Nvim starts: - "gO" is mapped in Normal mode to |vim.lsp.buf.document_symbol()| - CTRL-S is mapped in Insert mode to |vim.lsp.buf.signature_help()| -If not wanted, these keymaps can be removed at any time using -|vim.keymap.del()| or |:unmap| (see also |gr-default|). +You can remove GLOBAL keymaps at any time using |vim.keymap.del()| or +|:unmap|. See also |gr-default|. *lsp-defaults-disable* -To override or delete any of the above defaults, set or unset the options on -|LspAttach|: >lua +To remove or override BUFFER-LOCAL defaults, define a |LspAttach| handler: >lua vim.api.nvim_create_autocmd('LspAttach', { callback = function(args) @@ -789,7 +790,10 @@ buf_request_sync({bufnr}, {method}, {params}, {timeout_ms}) Parameters: ~ • {bufnr} (`integer`) Buffer handle, or 0 for current. • {method} (`string`) LSP method name - • {params} (`table?`) Parameters to send to the server + • {params} (`table|(fun(client: vim.lsp.Client, bufnr: integer): table?)?`) + Parameters to send to the server. Can also be passed as + a function that returns the params table for cases where + parameters are specific to the client. • {timeout_ms} (`integer?`, default: `1000`) Maximum time in milliseconds to wait for a result. @@ -809,61 +813,90 @@ client_is_stopped({client_id}) *vim.lsp.client_is_stopped()* (`boolean`) stopped true if client is stopped, false otherwise. commands *vim.lsp.commands* - Registry for client side commands. This is an extension point for plugins - to handle custom commands which are not part of the core language server - protocol specification. - - The registry is a table where the key is a unique command name, and the - value is a function which is called if any LSP action (code action, code - lenses, ...) triggers the command. - - If an LSP response contains a command for which no matching entry is - available in this registry, the command will be executed via the LSP - server using `workspace/executeCommand`. - - The first argument to the function will be the `Command`: Command title: - String command: String arguments?: any[] + Registry (a table) for client-side handlers, for custom server-commands + that are not in the LSP specification. + + If an LSP response contains a command which is not found in + `vim.lsp.commands`, the command will be executed via the LSP server using + `workspace/executeCommand`. + + Each key in the table is a unique command name, and each value is a + function which is called when an LSP action (code action, code lenses, + …) triggers the command. + • Argument 1 is the `Command`: > + Command + title: String + command: String + arguments?: any[] +< + • Argument 2 is the |lsp-handler| `ctx`. - The second argument is the `ctx` of |lsp-handler| + Example: >lua + vim.lsp.commands['java.action.generateToStringPrompt'] = function(_, ctx) + require("jdtls.async").run(function() + local _, result = request(ctx.bufnr, 'java/checkToStringStatus', ctx.params) + local fields = ui.pick_many(result.fields, 'Include item in toString?', function(x) + return string.format('%s: %s', x.name, x.type) + end) + local _, edit = request(ctx.bufnr, 'java/generateToString', { context = ctx.params; fields = fields; }) + vim.lsp.util.apply_workspace_edit(edit, offset_encoding) + end) + end +< config({name}, {cfg}) *vim.lsp.config()* - Update the configuration for an LSP client. - - Use name '*' to set default configuration for all clients. + Sets the default configuration for an LSP client (or all clients if the + special name "*" is used). - Can also be table-assigned to redefine the configuration for a client. + Can also be accessed by table-indexing (`vim.lsp.config[…]`) to get the + resolved config, or redefine the config (instead of "merging" with the + config chain). Examples: - • Add a root marker for all clients: >lua - vim.lsp.config('*', { - root_markers = { '.git' }, - }) + • Add root markers for ALL clients: >lua + vim.lsp.config('*', { + root_markers = { '.git', '.hg' }, + }) < - • Add additional capabilities to all clients: >lua - vim.lsp.config('*', { - capabilities = { - textDocument = { - semanticTokens = { - multilineTokenSupport = true, - } - } + • Add capabilities to ALL clients: >lua + vim.lsp.config('*', { + capabilities = { + textDocument = { + semanticTokens = { + multilineTokenSupport = true, } - }) + } + } + }) < - • (Re-)define the configuration for clangd: >lua - vim.lsp.config.clangd = { - cmd = { - 'clangd', - '--clang-tidy', - '--background-index', - '--offset-encoding=utf-8', - }, - root_markers = { '.clangd', 'compile_commands.json' }, - filetypes = { 'c', 'cpp' }, + • Add root markers and capabilities for "clangd": >lua + vim.lsp.config('clangd', { + root_markers = { '.clang-format', 'compile_commands.json' }, + capabilities = { + textDocument = { + completion = { + completionItem = { + snippetSupport = true, + } + } } + } + }) +< + • (Re-)define the "clangd" configuration (overrides the resolved chain): >lua + vim.lsp.config.clangd = { + cmd = { + 'clangd', + '--clang-tidy', + '--background-index', + '--offset-encoding=utf-8', + }, + root_markers = { '.clangd', 'compile_commands.json' }, + filetypes = { 'c', 'cpp' }, + } < - • Get configuration for luals: >lua - local cfg = vim.lsp.config.luals + • Get the resolved configuration for "luals": >lua + local cfg = vim.lsp.config.luals < Parameters: ~ @@ -1235,125 +1268,130 @@ Lua module: vim.lsp.client *lsp-client* *vim.lsp.ClientConfig* Fields: ~ - • {cmd} (`string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient`) - command string[] that launches the language - server (treated as in |jobstart()|, must be - absolute or on `$PATH`, shell constructs like - "~" are not expanded), or function that creates - an RPC client. Function receives a `dispatchers` - table and returns a table with member functions - `request`, `notify`, `is_closing` and - `terminate`. See |vim.lsp.rpc.request()|, - |vim.lsp.rpc.notify()|. For TCP there is a - builtin RPC client factory: - |vim.lsp.rpc.connect()| - • {cmd_cwd}? (`string`, default: cwd) Directory to launch the - `cmd` process. Not related to `root_dir`. - • {cmd_env}? (`table`) Environment flags to pass to the LSP - on spawn. Must be specified using a table. - Non-string values are coerced to string. - Example: >lua - { PORT = 8080; HOST = "0.0.0.0"; } + • {cmd} (`string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient`) + command string[] that launches the language + server (treated as in |jobstart()|, must be + absolute or on `$PATH`, shell constructs like + "~" are not expanded), or function that creates + an RPC client. Function receives a + `dispatchers` table and returns a table with + member functions `request`, `notify`, + `is_closing` and `terminate`. See + |vim.lsp.rpc.request()|, + |vim.lsp.rpc.notify()|. For TCP there is a + builtin RPC client factory: + |vim.lsp.rpc.connect()| + • {cmd_cwd}? (`string`, default: cwd) Directory to launch + the `cmd` process. Not related to `root_dir`. + • {cmd_env}? (`table`) Environment flags to pass to the LSP + on spawn. Must be specified using a table. + Non-string values are coerced to string. + Example: >lua + { PORT = 8080; HOST = "0.0.0.0"; } < - • {detached}? (`boolean`, default: true) Daemonize the server - process so that it runs in a separate process - group from Nvim. Nvim will shutdown the process - on exit, but if Nvim fails to exit cleanly this - could leave behind orphaned server processes. - • {workspace_folders}? (`lsp.WorkspaceFolder[]`) List of workspace - folders passed to the language server. For - backwards compatibility rootUri and rootPath - will be derived from the first workspace folder - in this list. See `workspaceFolders` in the LSP - spec. - • {capabilities}? (`lsp.ClientCapabilities`) Map overriding the - default capabilities defined by - |vim.lsp.protocol.make_client_capabilities()|, - passed to the language server on initialization. - Hint: use make_client_capabilities() and modify - its result. - • Note: To send an empty dictionary use - |vim.empty_dict()|, else it will be encoded as - an array. - • {handlers}? (`table<string,function>`) Map of language - server method names to |lsp-handler| - • {settings}? (`lsp.LSPObject`) Map with language server - specific settings. See the {settings} in - |vim.lsp.Client|. - • {commands}? (`table<string,fun(command: lsp.Command, ctx: table)>`) - Table that maps string of clientside commands to - user-defined functions. Commands passed to - `start()` take precedence over the global - command registry. Each key must be a unique - command name, and the value is a function which - is called if any LSP action (code action, code - lenses, ...) triggers the command. - • {init_options}? (`lsp.LSPObject`) Values to pass in the - initialization request as - `initializationOptions`. See `initialize` in the - LSP spec. - • {name}? (`string`, default: client-id) Name in log - messages. - • {get_language_id}? (`fun(bufnr: integer, filetype: string): string`) - Language ID as string. Defaults to the buffer - filetype. - • {offset_encoding}? (`'utf-8'|'utf-16'|'utf-32'`) Called "position - encoding" in LSP spec, the encoding that the LSP - server expects. Client does not verify this is - correct. - • {on_error}? (`fun(code: integer, err: string)`) Callback - invoked when the client operation throws an - error. `code` is a number describing the error. - Other arguments may be passed depending on the - error kind. See `vim.lsp.rpc.client_errors` for - possible errors. Use - `vim.lsp.rpc.client_errors[code]` to get - human-friendly name. - • {before_init}? (`fun(params: lsp.InitializeParams, config: vim.lsp.ClientConfig)`) - Callback invoked before the LSP "initialize" - phase, where `params` contains the parameters - being sent to the server and `config` is the - config that was passed to |vim.lsp.start()|. You - can use this to modify parameters before they - are sent. - • {on_init}? (`elem_or_list<fun(client: vim.lsp.Client, init_result: lsp.InitializeResult)>`) - Callback invoked after LSP "initialize", where - `result` is a table of `capabilities` and - anything else the server may send. For example, - clangd sends `init_result.offsetEncoding` if - `capabilities.offsetEncoding` was sent to it. - You can only modify the `client.offset_encoding` - here before any notifications are sent. - • {on_exit}? (`elem_or_list<fun(code: integer, signal: integer, client_id: integer)>`) - Callback invoked on client exit. - • code: exit code of the process - • signal: number describing the signal used to - terminate (if any) - • client_id: client handle - • {on_attach}? (`elem_or_list<fun(client: vim.lsp.Client, bufnr: integer)>`) - Callback invoked when client attaches to a - buffer. - • {trace}? (`'off'|'messages'|'verbose'`, default: "off") - Passed directly to the language server in the - initialize request. Invalid/empty values will - • {flags}? (`table`) A table with flags for the client. The - current (experimental) flags are: - • {allow_incremental_sync}? (`boolean`, default: - `true`) Allow using incremental sync for - buffer edits - • {debounce_text_changes} (`integer`, default: - `150`) Debounce `didChange` notifications to - the server by the given number in - milliseconds. No debounce occurs if `nil`. - • {exit_timeout} (`integer|false`, default: - `false`) Milliseconds to wait for server to - exit cleanly after sending the "shutdown" - request before sending kill -15. If set to - false, nvim exits immediately after sending - the "shutdown" request to the server. - • {root_dir}? (`string`) Directory where the LSP server will - base its workspaceFolders, rootUri, and rootPath - on initialization. + • {detached}? (`boolean`, default: true) Daemonize the server + process so that it runs in a separate process + group from Nvim. Nvim will shutdown the process + on exit, but if Nvim fails to exit cleanly this + could leave behind orphaned server processes. + • {workspace_folders}? (`lsp.WorkspaceFolder[]`) List of workspace + folders passed to the language server. For + backwards compatibility rootUri and rootPath + will be derived from the first workspace folder + in this list. See `workspaceFolders` in the LSP + spec. + • {workspace_required}? (`boolean`) (default false) Server requires a + workspace (no "single file" support). + • {capabilities}? (`lsp.ClientCapabilities`) Map overriding the + default capabilities defined by + |vim.lsp.protocol.make_client_capabilities()|, + passed to the language server on + initialization. Hint: use + make_client_capabilities() and modify its + result. + • Note: To send an empty dictionary use + |vim.empty_dict()|, else it will be encoded + as an array. + • {handlers}? (`table<string,function>`) Map of language + server method names to |lsp-handler| + • {settings}? (`lsp.LSPObject`) Map with language server + specific settings. See the {settings} in + |vim.lsp.Client|. + • {commands}? (`table<string,fun(command: lsp.Command, ctx: table)>`) + Table that maps string of clientside commands + to user-defined functions. Commands passed to + `start()` take precedence over the global + command registry. Each key must be a unique + command name, and the value is a function which + is called if any LSP action (code action, code + lenses, ...) triggers the command. + • {init_options}? (`lsp.LSPObject`) Values to pass in the + initialization request as + `initializationOptions`. See `initialize` in + the LSP spec. + • {name}? (`string`, default: client-id) Name in log + messages. + • {get_language_id}? (`fun(bufnr: integer, filetype: string): string`) + Language ID as string. Defaults to the buffer + filetype. + • {offset_encoding}? (`'utf-8'|'utf-16'|'utf-32'`) Called "position + encoding" in LSP spec, the encoding that the + LSP server expects. Client does not verify this + is correct. + • {on_error}? (`fun(code: integer, err: string)`) Callback + invoked when the client operation throws an + error. `code` is a number describing the error. + Other arguments may be passed depending on the + error kind. See `vim.lsp.rpc.client_errors` for + possible errors. Use + `vim.lsp.rpc.client_errors[code]` to get + human-friendly name. + • {before_init}? (`fun(params: lsp.InitializeParams, config: vim.lsp.ClientConfig)`) + Callback invoked before the LSP "initialize" + phase, where `params` contains the parameters + being sent to the server and `config` is the + config that was passed to |vim.lsp.start()|. + You can use this to modify parameters before + they are sent. + • {on_init}? (`elem_or_list<fun(client: vim.lsp.Client, init_result: lsp.InitializeResult)>`) + Callback invoked after LSP "initialize", where + `result` is a table of `capabilities` and + anything else the server may send. For example, + clangd sends `init_result.offsetEncoding` if + `capabilities.offsetEncoding` was sent to it. + You can only modify the + `client.offset_encoding` here before any + notifications are sent. + • {on_exit}? (`elem_or_list<fun(code: integer, signal: integer, client_id: integer)>`) + Callback invoked on client exit. + • code: exit code of the process + • signal: number describing the signal used to + terminate (if any) + • client_id: client handle + • {on_attach}? (`elem_or_list<fun(client: vim.lsp.Client, bufnr: integer)>`) + Callback invoked when client attaches to a + buffer. + • {trace}? (`'off'|'messages'|'verbose'`, default: "off") + Passed directly to the language server in the + initialize request. Invalid/empty values will + • {flags}? (`table`) A table with flags for the client. + The current (experimental) flags are: + • {allow_incremental_sync}? (`boolean`, + default: `true`) Allow using incremental sync + for buffer edits + • {debounce_text_changes} (`integer`, default: + `150`) Debounce `didChange` notifications to + the server by the given number in + milliseconds. No debounce occurs if `nil`. + • {exit_timeout} (`integer|false`, default: + `false`) Milliseconds to wait for server to + exit cleanly after sending the "shutdown" + request before sending kill -15. If set to + false, nvim exits immediately after sending + the "shutdown" request to the server. + • {root_dir}? (`string`) Directory where the LSP server will + base its workspaceFolders, rootUri, and + rootPath on initialization. Client:cancel_request({id}) *Client:cancel_request()* diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index ceb8184eef..be118cf790 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -188,15 +188,19 @@ Examples: >lua ============================================================================== IMPORTING LUA MODULES *lua-module-load* -Modules are searched for under the directories specified in 'runtimepath', in -the order they appear. Any "." in the module name is treated as a directory -separator when searching. For a module `foo.bar`, each directory is searched -for `lua/foo/bar.lua`, then `lua/foo/bar/init.lua`. If no files are found, -the directories are searched again for a shared library with a name matching -`lua/foo/bar.?`, where `?` is a list of suffixes (such as `so` or `dll`) derived from -the initial value of |package.cpath|. If still no files are found, Nvim falls -back to Lua's default search mechanism. The first script found is run and -`require()` returns the value returned by the script if any, else `true`. +Modules are searched for under the directories specified in 'runtimepath' and +|packages-runtimepath|, in the order they appear in the output of this command +>vim + :echo nvim_list_runtime_paths() +< +Any "." in the module name is treated as a directory separator when searching. +For a module `foo.bar`, each directory is searched for `lua/foo/bar.lua`, then +`lua/foo/bar/init.lua`. If no files are found, the directories are searched +again for a shared library with a name matching `lua/foo/bar.?`, where `?` is +a list of suffixes (such as `so` or `dll`) derived from the initial value of +|package.cpath|. If still no files are found, Nvim falls back to Lua's default +search mechanism. The first script found is run and `require()` returns the +value returned by the script if any, else `true`. The return value is cached after the first call to `require()` for each module, with subsequent calls returning the cached value without searching for, or @@ -214,56 +218,9 @@ and loads the first module found ("first wins"): > bar/lua/mod.so bar/lua/mod.dll < - *lua-package-path* -Nvim automatically adjusts |package.path| and |package.cpath| according to the -effective 'runtimepath' value. Adjustment happens whenever 'runtimepath' is -changed. `package.path` is adjusted by simply appending `/lua/?.lua` and -`/lua/?/init.lua` to each directory from 'runtimepath' (`/` is actually the -first character of `package.config`). - -Similarly to |package.path|, modified directories from 'runtimepath' are also -added to |package.cpath|. In this case, instead of appending `/lua/?.lua` and -`/lua/?/init.lua` to each runtimepath, all unique `?`-containing suffixes of -the existing |package.cpath| are used. Example: - -- 1. Given that - - 'runtimepath' contains `/foo/bar,/xxx;yyy/baz,/abc`; - - initial |package.cpath| (defined at compile-time or derived from - `$LUA_CPATH` / `$LUA_INIT`) contains `./?.so;/def/ghi/a?d/j/g.elf;/def/?.so`. -- 2. It finds `?`-containing suffixes `/?.so`, `/a?d/j/g.elf` and `/?.so`, in - order: parts of the path starting from the first path component containing - question mark and preceding path separator. -- 3. The suffix of `/def/?.so`, namely `/?.so` is not unique, as it’s the same - as the suffix of the first path from |package.path| (i.e. `./?.so`). Which - leaves `/?.so` and `/a?d/j/g.elf`, in this order. -- 4. 'runtimepath' has three paths: `/foo/bar`, `/xxx;yyy/baz` and `/abc`. The - second one contains a semicolon which is a paths separator so it is out, - leaving only `/foo/bar` and `/abc`, in order. -- 5. The cartesian product of paths from 4. and suffixes from 3. is taken, - giving four variants. In each variant a `/lua` path segment is inserted - between path and suffix, leaving: - - `/foo/bar/lua/?.so` - - `/foo/bar/lua/a?d/j/g.elf` - - `/abc/lua/?.so` - - `/abc/lua/a?d/j/g.elf` -- 6. New paths are prepended to the original |package.cpath|. - -The result will look like this: > - - /foo/bar,/xxx;yyy/baz,/abc ('runtimepath') - × ./?.so;/def/ghi/a?d/j/g.elf;/def/?.so (package.cpath) - = /foo/bar/lua/?.so;/foo/bar/lua/a?d/j/g.elf;/abc/lua/?.so;/abc/lua/a?d/j/g.elf;./?.so;/def/ghi/a?d/j/g.elf;/def/?.so - Note: -- To track 'runtimepath' updates, paths added at previous update are - remembered and removed at the next update, while all paths derived from the - new 'runtimepath' are prepended as described above. This allows removing - paths when path is removed from 'runtimepath', adding paths when they are - added and reordering |package.path|/|package.cpath| content if 'runtimepath' - was reordered. - -- Although adjustments happen automatically, Nvim does not track current +- Although 'runtimepath' is tracked, Nvim does not track current values of |package.path| or |package.cpath|. If you happen to delete some paths from there you can set 'runtimepath' to trigger an update: >vim let &runtimepath = &runtimepath diff --git a/runtime/doc/news-0.10.txt b/runtime/doc/news-0.10.txt index 1399be0d24..7951bbeaaa 100644 --- a/runtime/doc/news-0.10.txt +++ b/runtime/doc/news-0.10.txt @@ -213,7 +213,7 @@ The following new features were added. • By default, the swapfile "ATTENTION" |E325| dialog is skipped if the swapfile is owned by a running Nvim process, instead of prompting. If you always want the swapfile dialog, delete the default SwapExists handler: - `autocmd! nvim_swapfile`. |default-autocmds| + `autocmd! nvim.swapfile`. |default-autocmds| • Navigating the |jumplist| with CTRL+O, CTRL+I behaves more intuitively when deleting buffers, and avoids "invalid buffer" cases. #25461 • |:fclose| command. diff --git a/runtime/doc/news-0.11.txt b/runtime/doc/news-0.11.txt index f934456c3a..36d96b8842 100644 --- a/runtime/doc/news-0.11.txt +++ b/runtime/doc/news-0.11.txt @@ -56,6 +56,9 @@ EDITOR it moves to another help buffer. • Bells from a |terminal| buffer are now silent by default, unless 'belloff' option doesn't contain "term" or "all". +• Renamed autocmd group `nvim_swapfile` to `nvim.swapfile`. |default-autocmds| + If you always want the swapfile dialog, delete the `nvim.swapfile` group: + `autocmd! nvim.swapfile`. EVENTS @@ -288,7 +291,7 @@ LUA • Command-line completions for: `vim.g`, `vim.t`, `vim.w`, `vim.b`, `vim.v`, `vim.o`, `vim.wo`, `vim.bo`, `vim.opt`, `vim.opt_local`, `vim.opt_global`, - and `vim.fn`. + `vim.env` and `vim.fn`. • Documentation for |lua-bit|. • |gf| in Lua buffers can go to module in same repo, |runtime-search-path| and |package.path|. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index ad1481e304..2644bb24b2 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -66,7 +66,7 @@ EVENTS HIGHLIGHTS -• todo +• |hl-DiffTextAdd| highlights added text within a changed line. LSP @@ -78,7 +78,8 @@ LUA OPTIONS -• todo +• 'diffopt' `inline:` configures diff highlighting for changes within a line. +• 'pummaxwidth' sets maximum width for the completion popup menu. PLUGINS @@ -123,7 +124,7 @@ EVENTS LSP -• todo +• |vim.lsp.ClientConfig| gained `workspace_required`. LUA diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index dd2460722e..bdc4f20fac 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2056,7 +2056,7 @@ A jump table for the options with a short description can be found at |Q_op|. security reasons. *'diffopt'* *'dip'* -'diffopt' 'dip' string (default "internal,filler,closeoff,linematch:40") +'diffopt' 'dip' string (default "internal,filler,closeoff,inline:simple,linematch:40") global Option settings for diff mode. It can consist of the following items. All are optional. Items must be separated by a comma. @@ -2119,6 +2119,21 @@ A jump table for the options with a short description can be found at |Q_op|. Use the indent heuristic for the internal diff library. + inline:{text} Highlight inline differences within a change. + See |view-diffs|. Supported values are: + + none Do not perform inline highlighting. + simple Highlight from first different + character to the last one in each + line. This is the default if no + `inline:` value is set. + char Use internal diff to perform a + character-wise diff and highlight the + difference. + word Use internal diff to perform a + |word|-wise diff and highlight the + difference. + internal Use the internal diff library. This is ignored when 'diffexpr' is set. *E960* When running out of memory when writing a @@ -4608,6 +4623,14 @@ A jump table for the options with a short description can be found at |Q_op|. Maximum number of items to show in the popup menu (|ins-completion-menu|). Zero means "use available screen space". + *'pummaxwidth'* *'pmw'* +'pummaxwidth' 'pmw' number (default 0) + global + Maximum width for the popup menu (|ins-completion-menu|). When zero, + there is no maximum width limit, otherwise the popup menu will never be + wider than this value. Truncated text will be indicated by "..." at the + end. Takes precedence over 'pumwidth'. + *'pumwidth'* *'pw'* 'pumwidth' 'pw' number (default 15) global diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt index be913e941e..ec1eb9a4a0 100644 --- a/runtime/doc/pattern.txt +++ b/runtime/doc/pattern.txt @@ -1206,7 +1206,8 @@ x A single character, with no special meaning, matches itself \o40 octal number of character up to 0o377 \x20 hexadecimal number of character up to 0xff \u20AC hex. number of multibyte character up to 0xffff - \U1234 hex. number of multibyte character up to 0xffffffff + \U1234 hex. number of multibyte character up to 8 characters + 0xffffffff |E1541| NOTE: The other backslash codes mentioned above do not work inside []! - Matching with a collection can be slow, because each character in @@ -1246,7 +1247,8 @@ x A single character, with no special meaning, matches itself \%u20AC Matches the character specified with up to four hexadecimal characters. \%U1234abcd Matches the character specified with up to eight hexadecimal - characters, up to 0x7fffffff + characters, up to 0x7fffffff (the maximum allowed value is INT_MAX + |E1541|, but the maximum valid Unicode codepoint is U+10FFFF). ============================================================================== 7. Ignoring case in a pattern */ignorecase* diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt index 38bf6281d3..7a4cc0ee7d 100644 --- a/runtime/doc/provider.txt +++ b/runtime/doc/provider.txt @@ -11,7 +11,7 @@ the providers and how to install them. *E319* Use of a feature requiring a missing provider is an error: > - E319: No "foo" provider found. Run ":checkhealth provider" + E319: No "foo" provider found. Run ":checkhealth vim.provider" Run the |:checkhealth| command, and review the sections below. @@ -35,7 +35,7 @@ if you already have it (some package managers install the module with Nvim itself). For Python 3 plugins: -1. Make sure Python 3.4+ is available in your $PATH. +1. Make sure Python 3.9+ is available in your $PATH. 2. Install the module (try "python" if "python3" is missing): >bash python3 -m pip install --user --upgrade pynvim diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index faca3d88da..93a385359d 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -1723,12 +1723,12 @@ cycles for such a feature to become either integrated into the platform or withdrawn from this effort. To cater for early adopters, there is optional support in Vim for syntax related preview features that are implemented. You can request it by specifying a list of preview feature numbers as follows: > - :let g:java_syntax_previews = [455, 476] + :let g:java_syntax_previews = [488, 494] The supported JEP numbers are to be drawn from this table: `430`: String Templates [JDK 21] - `455`: Primitive types in Patterns, instanceof, and switch - `476`: Module Import Declarations + `488`: Primitive types in Patterns, instanceof, and switch + `494`: Module Import Declarations Note that as soon as the particular preview feature will have been integrated into the Java platform, its entry will be removed from the table and related @@ -5177,8 +5177,11 @@ DiffChange Diff mode: Changed line. |diff.txt| DiffDelete Diff mode: Deleted line. |diff.txt| *hl-DiffText* DiffText Diff mode: Changed text within a changed line. |diff.txt| + *hl-DiffTextAdd* +DiffTextAdd Diff mode: Added text within a changed line. Linked to + |hl-DiffText| by default. |diff.txt| *hl-EndOfBuffer* -EndOfBuffer Filler lines (~) after the end of the buffer. +EndOfBuffer Filler lines (~) after the last line in the buffer. By default, this is highlighted like |hl-NonText|. *hl-TermCursor* TermCursor Cursor in a focused terminal. diff --git a/runtime/doc/vi_diff.txt b/runtime/doc/vi_diff.txt index 0a0cbc8ec6..c6de169853 100644 --- a/runtime/doc/vi_diff.txt +++ b/runtime/doc/vi_diff.txt @@ -31,8 +31,11 @@ Maximum display width Unix and Win32: 1024 characters, otherwise 255 Maximum lhs of a mapping 50 characters. Number of different highlighting types: over 30000 Range of a Number variable: -2147483648 to 2147483647 (might be more on 64 - bit systems) + bit systems) See also: |v:numbermax|, + |v:numbermin| and |v:numbersize| Maximum length of a line in a tags file: 512 bytes. + *E1541* +Maximum value for |/\U| and |/\%U|: 2147483647 (for 32bit integer). Information for undo and text in registers is kept in memory, thus when making (big) changes the amount of (virtual) memory available limits the number of diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index df79d25198..a5c72363b4 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -48,7 +48,7 @@ Defaults *defaults* *nvim-defaults* - 'complete' excludes "i" - 'completeopt' defaults to "menu,popup" - 'define' defaults to "". The C ftplugin sets it to "^\\s*#\\s*define" -- 'diffopt' defaults to "internal,filler,closeoff,linematch:40" +- 'diffopt' defaults to "internal,filler,closeoff,inline:simple,linematch:40" - 'directory' defaults to ~/.local/state/nvim/swap// (|xdg|), auto-created - 'display' defaults to "lastline" - 'encoding' is UTF-8 (cf. 'fileencoding' for file-content encoding) diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 7440af1a96..3cb38be450 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -622,10 +622,10 @@ do end vim.wo[0][0].winhighlight = winhl .. 'StatusLine:StatusLineTerm,StatusLineNC:StatusLineTermNC' - vim.keymap.set('n', '[[', function() + vim.keymap.set({ 'n', 'x' }, '[[', 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() + vim.keymap.set({ 'n', 'x' }, ']]', 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, diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index e7ad91132d..62df0a7707 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -939,8 +939,8 @@ function vim.api.nvim_create_augroup(name, opts) end --- - desc (string) optional: description (for documentation and troubleshooting). --- - callback (function|string) optional: Lua function (or Vimscript function name, if --- string) called when the event(s) is triggered. Lua callback can return a truthy ---- value (not `false` or `nil`) to delete the autocommand. Receives one argument, ---- a table with these keys: [event-args]() +--- value (not `false` or `nil`) to delete the autocommand, and receives one argument, a +--- table with these keys: [event-args]() --- - id: (number) autocommand id --- - event: (string) name of the triggered event `autocmd-events` --- - group: (number|nil) autocommand group id, if any @@ -1796,41 +1796,40 @@ function vim.api.nvim_open_term(buffer, opts) end --- region is hidden by setting `eob` flag of --- 'fillchars' to a space char, and clearing the --- `hl-EndOfBuffer` region in 'winhighlight'. ---- - border: Style of (optional) window border. This can either be a string ---- or an array. The string values are the same as those described in 'winborder'. ---- If it is an array, it should have a length of eight or any divisor of ---- eight. The array will specify the eight chars building up the border ---- in a clockwise fashion starting with the top-left corner. As an ---- example, the double box style could be specified as: ---- ``` ---- [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. ---- ``` ---- If the number of chars are less than eight, they will be repeated. Thus ---- an ASCII border could be specified as ---- ``` ---- [ "/", "-", \"\\\\\", "|" ], ---- ``` ---- or all chars the same as ---- ``` ---- [ "x" ]. ---- ``` ---- An empty string can be used to turn off a specific border, for instance, +--- - border: (`string|string[]`) (defaults to 'winborder' option) Window border. The string form +--- accepts the same values as the 'winborder' option. The array form must have a length of +--- eight or any divisor of eight, specifying the chars that form the border in a clockwise +--- fashion starting from the top-left corner. For example, the double-box style can be +--- specified as: --- ``` ---- [ "", "", "", ">", "", "", "", "<" ] +--- [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. --- ``` ---- will only make vertical borders but not horizontal ones. ---- By default, `FloatBorder` highlight is used, which links to `WinSeparator` ---- when not defined. It could also be specified by character: +--- If fewer than eight chars are given, they will be repeated. An ASCII border could be +--- specified as: --- ``` ---- [ ["+", "MyCorner"], ["x", "MyBorder"] ]. +--- [ "/", "-", \"\\\\\", "|" ], --- ``` ---- - title: Title (optional) in window border, string or list. +--- Or one char for all sides: +--- ``` +--- [ "x" ]. +--- ``` +--- Empty string can be used to hide a specific border. This example will show only vertical +--- borders, not horizontal: +--- ``` +--- [ "", "", "", ">", "", "", "", "<" ] +--- ``` +--- By default, `hl-FloatBorder` highlight is used, which links to `hl-WinSeparator` when not +--- defined. Each border side can specify an optional highlight: +--- ``` +--- [ ["+", "MyCorner"], ["x", "MyBorder"] ]. +--- ``` +--- - title: (optional) Title in window border, string or list. --- List should consist of `[text, highlight]` tuples. --- If string, or a tuple lacks a highlight, the default highlight group is `FloatTitle`. --- - title_pos: Title position. Must be set with `title` option. --- Value can be one of "left", "center", or "right". --- Default is `"left"`. ---- - footer: Footer (optional) in window border, string or list. +--- - footer: (optional) Footer in window border, string or list. --- List should consist of `[text, highlight]` tuples. --- If string, or a tuple lacks a highlight, the default highlight group is `FloatFooter`. --- - footer_pos: Footer position. Must be set with `footer` option. diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 20d8ac3058..9ff4770e7c 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -1716,6 +1716,21 @@ vim.go.dex = vim.go.diffexpr --- Use the indent heuristic for the internal --- diff library. --- +--- inline:{text} Highlight inline differences within a change. +--- See `view-diffs`. Supported values are: +--- +--- none Do not perform inline highlighting. +--- simple Highlight from first different +--- character to the last one in each +--- line. This is the default if no +--- `inline:` value is set. +--- char Use internal diff to perform a +--- character-wise diff and highlight the +--- difference. +--- word Use internal diff to perform a +--- `word`-wise diff and highlight the +--- difference. +--- --- internal Use the internal diff library. This is --- ignored when 'diffexpr' is set. *E960* --- When running out of memory when writing a @@ -1766,7 +1781,7 @@ vim.go.dex = vim.go.diffexpr --- --- --- @type string -vim.o.diffopt = "internal,filler,closeoff,linematch:40" +vim.o.diffopt = "internal,filler,closeoff,inline:simple,linematch:40" vim.o.dip = vim.o.diffopt vim.go.diffopt = vim.o.diffopt vim.go.dip = vim.go.diffopt @@ -4787,6 +4802,17 @@ vim.o.ph = vim.o.pumheight vim.go.pumheight = vim.o.pumheight vim.go.ph = vim.go.pumheight +--- Maximum width for the popup menu (`ins-completion-menu`). When zero, +--- there is no maximum width limit, otherwise the popup menu will never be +--- wider than this value. Truncated text will be indicated by "..." at the +--- end. Takes precedence over 'pumwidth'. +--- +--- @type integer +vim.o.pummaxwidth = 0 +vim.o.pmw = vim.o.pummaxwidth +vim.go.pummaxwidth = vim.o.pummaxwidth +vim.go.pmw = vim.go.pummaxwidth + --- Minimum width for the popup menu (`ins-completion-menu`). If the --- cursor column + 'pumwidth' exceeds screen width, the popup menu is --- nudged to fit on the screen. diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index c790779d51..294f2a357a 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -275,7 +275,7 @@ end --- --- @param msg string function M.ok(msg) - local input = format_report_message('OK', msg) + local input = format_report_message('✅ OK', msg) collect_output(input) end @@ -284,7 +284,7 @@ end --- @param msg string --- @param ... string|string[] Optional advice function M.warn(msg, ...) - local input = format_report_message('WARNING', msg, ...) + local input = format_report_message('⚠️ WARNING', msg, ...) collect_output(input) end @@ -293,7 +293,7 @@ end --- @param msg string --- @param ... string|string[] Optional advice function M.error(msg, ...) - local input = format_report_message('ERROR', msg, ...) + local input = format_report_message('❌ ERROR', msg, ...) collect_output(input) end @@ -449,11 +449,15 @@ function M._check(mods, plugin_names) vim.print('') -- Quit with 'q' inside healthcheck buffers. - vim.keymap.set('n', 'q', function() - if not pcall(vim.cmd.close) then - vim.cmd.bdelete() + vim._with({ buf = bufnr }, function() + if vim.fn.maparg('q', 'n', false, false) == '' then + vim.keymap.set('n', 'q', function() + if not pcall(vim.cmd.close) then + vim.cmd.bdelete() + end + end, { buffer = bufnr, silent = true, noremap = true, nowait = true }) end - end, { buffer = bufnr, silent = true, noremap = true, nowait = true }) + end) -- Once we're done writing checks, set nomodifiable. vim.bo[bufnr].modifiable = false diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index bb4e1cd28f..995340d751 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -296,49 +296,64 @@ end --- root_dir matches. --- @field reuse_client? fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean ---- Update the configuration for an LSP client. +--- Sets the default configuration for an LSP client (or _all_ clients if the special name "*" is +--- used). --- ---- Use name '*' to set default configuration for all clients. ---- ---- Can also be table-assigned to redefine the configuration for a client. +--- Can also be accessed by table-indexing (`vim.lsp.config[…]`) to get the resolved config, or +--- redefine the config (instead of "merging" with the config chain). --- --- Examples: --- ---- - Add a root marker for all clients: +--- - Add root markers for ALL clients: +--- ```lua +--- vim.lsp.config('*', { +--- root_markers = { '.git', '.hg' }, +--- }) +--- ``` +--- - Add capabilities to ALL clients: --- ```lua ---- vim.lsp.config('*', { ---- root_markers = { '.git' }, ---- }) ---- ``` ---- - Add additional capabilities to all clients: +--- vim.lsp.config('*', { +--- capabilities = { +--- textDocument = { +--- semanticTokens = { +--- multilineTokenSupport = true, +--- } +--- } +--- } +--- }) +--- ``` +--- - Add root markers and capabilities for "clangd": --- ```lua ---- vim.lsp.config('*', { ---- capabilities = { ---- textDocument = { ---- semanticTokens = { ---- multilineTokenSupport = true, ---- } ---- } ---- } ---- }) ---- ``` ---- - (Re-)define the configuration for clangd: +--- vim.lsp.config('clangd', { +--- root_markers = { '.clang-format', 'compile_commands.json' }, +--- capabilities = { +--- textDocument = { +--- completion = { +--- completionItem = { +--- snippetSupport = true, +--- } +--- } +--- } +--- } +--- }) +--- ``` +--- - (Re-)define the "clangd" configuration (overrides the resolved chain): --- ```lua ---- vim.lsp.config.clangd = { ---- cmd = { ---- 'clangd', ---- '--clang-tidy', ---- '--background-index', ---- '--offset-encoding=utf-8', ---- }, ---- root_markers = { '.clangd', 'compile_commands.json' }, ---- filetypes = { 'c', 'cpp' }, ---- } ---- ``` ---- - Get configuration for luals: +--- vim.lsp.config.clangd = { +--- cmd = { +--- 'clangd', +--- '--clang-tidy', +--- '--background-index', +--- '--offset-encoding=utf-8', +--- }, +--- root_markers = { '.clangd', 'compile_commands.json' }, +--- filetypes = { 'c', 'cpp' }, +--- } +--- ``` +--- - Get the resolved configuration for "luals": --- ```lua ---- local cfg = vim.lsp.config.luals ---- ``` +--- local cfg = vim.lsp.config.luals +--- ``` --- --- @param name string --- @param cfg vim.lsp.Config @@ -615,6 +630,17 @@ function lsp.start(config, opts) config.root_dir = vim.fs.root(bufnr, opts._root_markers) end + if + not config.root_dir + and (not config.workspace_folders or #config.workspace_folders == 0) + and config.workspace_required + then + log.info( + ('skipping config "%s": workspace_required=true, no workspace found'):format(config.name) + ) + return + end + for _, client in pairs(all_clients) do if reuse_client(client, config) then if opts.attach == false then @@ -1230,7 +1256,9 @@ end --- ---@param bufnr integer Buffer handle, or 0 for current. ---@param method string LSP method name ----@param params table? Parameters to send to the server +---@param params? table|(fun(client: vim.lsp.Client, bufnr: integer): table?) Parameters to send to the server. +--- Can also be passed as a function that returns the params table for cases where +--- parameters are specific to the client. ---@param timeout_ms integer? Maximum time in milliseconds to wait for a result. --- (default: `1000`) ---@return table<integer, {error: lsp.ResponseError?, result: any}>? result Map of client_id:request_result. @@ -1511,25 +1539,39 @@ function lsp.with(handler, override_config) end end ---- Registry for client side commands. ---- This is an extension point for plugins to handle custom commands which are ---- not part of the core language server protocol specification. +--- Registry (a table) for client-side handlers, for custom server-commands that are not in the LSP +--- specification. --- ---- The registry is a table where the key is a unique command name, ---- and the value is a function which is called if any LSP action ---- (code action, code lenses, ...) triggers the command. +--- If an LSP response contains a command which is not found in `vim.lsp.commands`, the command will +--- be executed via the LSP server using `workspace/executeCommand`. --- ---- If an LSP response contains a command for which no matching entry is ---- available in this registry, the command will be executed via the LSP server ---- using `workspace/executeCommand`. +--- Each key in the table is a unique command name, and each value is a function which is called +--- when an LSP action (code action, code lenses, …) triggers the command. --- ---- The first argument to the function will be the `Command`: +--- - Argument 1 is the `Command`: +--- ``` --- Command --- title: String --- command: String --- arguments?: any[] +--- ``` +--- - Argument 2 is the |lsp-handler| `ctx`. +--- +--- Example: +--- +--- ```lua +--- vim.lsp.commands['java.action.generateToStringPrompt'] = function(_, ctx) +--- require("jdtls.async").run(function() +--- local _, result = request(ctx.bufnr, 'java/checkToStringStatus', ctx.params) +--- local fields = ui.pick_many(result.fields, 'Include item in toString?', function(x) +--- return string.format('%s: %s', x.name, x.type) +--- end) +--- local _, edit = request(ctx.bufnr, 'java/generateToString', { context = ctx.params; fields = fields; }) +--- vim.lsp.util.apply_workspace_edit(edit, offset_encoding) +--- end) +--- end +--- ``` --- ---- The second argument is the `ctx` of |lsp-handler| --- @type table<string,function> lsp.commands = setmetatable({}, { __newindex = function(tbl, key, value) diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 8c75ee321d..b256eab1a6 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -63,6 +63,9 @@ local validate = vim.validate --- folder in this list. See `workspaceFolders` in the LSP spec. --- @field workspace_folders? lsp.WorkspaceFolder[] --- +--- (default false) Server requires a workspace (no "single file" support). +--- @field workspace_required? boolean +--- --- Map overriding the default capabilities defined by |vim.lsp.protocol.make_client_capabilities()|, --- passed to the language server on initialization. Hint: use make_client_capabilities() and modify --- its result. diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua index 8af9f2f791..04e8393eb3 100644 --- a/runtime/lua/vim/lsp/health.lua +++ b/runtime/lua/vim/lsp/health.lua @@ -187,26 +187,32 @@ local function check_enabled_configs() local config = vim.lsp.config[name] local text = {} --- @type string[] text[#text + 1] = ('%s:'):format(name) - for k, v in - vim.spairs(config --[[@as table<string,any>]]) - do - local v_str --- @type string? - if k == 'name' then - v_str = nil - elseif k == 'filetypes' or k == 'root_markers' then - v_str = table.concat(v, ', ') - elseif type(v) == 'function' then - v_str = func_tostring(v) - else - v_str = vim.inspect(v, { newline = '\n ' }) - end + if not config then + report_warn( + ("'%s' config not found. Ensure that vim.lsp.config('%s') was called."):format(name, name) + ) + else + for k, v in + vim.spairs(config --[[@as table<string,any>]]) + do + local v_str --- @type string? + if k == 'name' then + v_str = nil + elseif k == 'filetypes' or k == 'root_markers' then + v_str = table.concat(v, ', ') + elseif type(v) == 'function' then + v_str = func_tostring(v) + else + v_str = vim.inspect(v, { newline = '\n ' }) + end - if k == 'cmd' and type(v) == 'table' and vim.fn.executable(v[1]) == 0 then - report_warn(("'%s' is not executable. Configuration will not be used."):format(v[1])) - end + if k == 'cmd' and type(v) == 'table' and vim.fn.executable(v[1]) == 0 then + report_warn(("'%s' is not executable. Configuration will not be used."):format(v[1])) + end - if v_str then - text[#text + 1] = ('- %s: %s'):format(k, v_str) + if v_str then + text[#text + 1] = ('- %s: %s'):format(k, v_str) + end end end text[#text + 1] = '' diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index ab3a269937..a37fa42aac 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -94,10 +94,10 @@ function M.on_refresh(err, _, ctx) for _, bufnr in ipairs(vim.lsp.get_buffers_by_client_id(ctx.client_id)) do for _, winid in ipairs(api.nvim_list_wins()) do if api.nvim_win_get_buf(winid) == bufnr then - if bufstates[bufnr] then + if bufstates[bufnr] and bufstates[bufnr].enabled then bufstates[bufnr].applied = {} + util._refresh(ms.textDocument_inlayHint, { bufnr = bufnr }) end - util._refresh(ms.textDocument_inlayHint, { bufnr = bufnr }) end end end diff --git a/runtime/lua/vim/provider/health.lua b/runtime/lua/vim/provider/health.lua index fa01951b02..e4b5206fa4 100644 --- a/runtime/lua/vim/provider/health.lua +++ b/runtime/lua/vim/provider/health.lua @@ -107,9 +107,22 @@ local function provider_disabled(provider) return false end +--- Checks the hygiene of a `g:loaded_xx_provider` variable. +local function check_loaded_var(var) + if vim.g[var] == 1 then + health.error(('`g:%s=1` may have been set by mistake.'):format(var), { + ('Remove `vim.g.%s=1` from your config.'):format(var), + 'To disable the provider, set this to 0, not 1.', + 'If you want to enable the provider but skip automatic detection, set the respective `g:…_host_prog` var. See :help provider', + }) + end +end + local function clipboard() health.start('Clipboard (optional)') + check_loaded_var('loaded_clipboard_provider') + if os.getenv('TMUX') and vim.fn.executable('tmux') == 1 @@ -144,6 +157,8 @@ end local function node() health.start('Node.js provider (optional)') + check_loaded_var('loaded_node_provider') + if provider_disabled('node') then return end @@ -247,6 +262,8 @@ end local function perl() health.start('Perl provider (optional)') + check_loaded_var('loaded_perl_provider') + if provider_disabled('perl') then return end @@ -256,7 +273,7 @@ local function perl() if not perl_exec then health.warn(assert(perl_warnings), { 'See :help provider-perl for more information.', - 'You may disable this provider (and warning) by adding `let g:loaded_perl_provider = 0` to your init.vim', + 'You can disable this provider (and warning) by adding `let g:loaded_perl_provider = 0` to your init.vim', }) health.warn('No usable perl executable found') return @@ -547,7 +564,7 @@ local function version_info(python) local nvim_path_base = vim.fn.fnamemodify(nvim_path, [[:~:h]]) local version_status = 'unknown; ' .. nvim_path_base - if is_bad_response(nvim_version) and is_bad_response(pypi_version) then + if not is_bad_response(nvim_version) and not is_bad_response(pypi_version) then if vim.version.lt(nvim_version, pypi_version) then version_status = 'outdated; from ' .. nvim_path_base else @@ -561,6 +578,8 @@ end local function python() health.start('Python 3 provider (optional)') + check_loaded_var('loaded_python3_provider') + local python_exe = '' local virtual_env = os.getenv('VIRTUAL_ENV') local venv = virtual_env and vim.fn.resolve(virtual_env) or '' @@ -595,7 +614,7 @@ local function python() if pythonx_warnings then health.warn(pythonx_warnings, { 'See :help provider-python for more information.', - 'You may disable this provider (and warning) by adding `let g:loaded_python3_provider = 0` to your init.vim', + 'You can disable this provider (and warning) by adding `let g:loaded_python3_provider = 0` to your init.vim', }) elseif pyname and pyname ~= '' and python_exe == '' then if not vim.g[host_prog_var] then @@ -840,6 +859,8 @@ end local function ruby() health.start('Ruby provider (optional)') + check_loaded_var('loaded_ruby_provider') + if provider_disabled('ruby') then return end @@ -860,7 +881,7 @@ local function ruby() 'Run `gem environment` to ensure the gem bin directory is in $PATH.', 'If you are using rvm/rbenv/chruby, try "rehashing".', 'See :help g:ruby_host_prog for non-standard gem installations.', - 'You may disable this provider (and warning) by adding `let g:loaded_ruby_provider = 0` to your init.vim', + 'You can disable this provider (and warning) by adding `let g:loaded_ruby_provider = 0` to your init.vim', }) return end diff --git a/runtime/lua/vim/provider/python.lua b/runtime/lua/vim/provider/python.lua index 48f08302f9..a772b36973 100644 --- a/runtime/lua/vim/provider/python.lua +++ b/runtime/lua/vim/provider/python.lua @@ -1,5 +1,5 @@ local M = {} -local min_version = '3.7' +local min_version = '3.9' local s_err ---@type string? local s_host ---@type string? diff --git a/runtime/lua/vim/treesitter/_headings.lua b/runtime/lua/vim/treesitter/_headings.lua index 885d014a89..bfa468ad88 100644 --- a/runtime/lua/vim/treesitter/_headings.lua +++ b/runtime/lua/vim/treesitter/_headings.lua @@ -40,10 +40,6 @@ local heading_queries = { ]], } -local function hash_tick(bufnr) - return tostring(vim.b[bufnr].changedtick) -end - ---@class TS.Heading ---@field bufnr integer ---@field lnum integer @@ -53,7 +49,7 @@ end --- Extract headings from buffer --- @param bufnr integer buffer to extract headings from --- @return TS.Heading[] -local get_headings = vim.func._memoize(hash_tick, function(bufnr) +local get_headings = function(bufnr) local lang = ts.language.get_lang(vim.bo[bufnr].filetype) if not lang then return {} @@ -85,7 +81,7 @@ local get_headings = vim.func._memoize(hash_tick, function(bufnr) end end return headings -end) +end --- Shows an Outline (table of contents) of the current buffer, in the loclist. function M.show_toc() diff --git a/runtime/lua/vim/treesitter/_meta/misc.lua b/runtime/lua/vim/treesitter/_meta/misc.lua index 07a1c921c7..9b9cc4eb54 100644 --- a/runtime/lua/vim/treesitter/_meta/misc.lua +++ b/runtime/lua/vim/treesitter/_meta/misc.lua @@ -5,12 +5,10 @@ error('Cannot require a meta file') ---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string) ---@class TSParser: userdata ----@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean): TSTree, (Range4|Range6)[] +---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean, timeout_ns: integer?): TSTree?, (Range4|Range6)[] ---@field reset fun(self: TSParser) ---@field included_ranges fun(self: TSParser, include_bytes: boolean?): integer[] ---@field set_included_ranges fun(self: TSParser, ranges: (Range6|TSNode)[]) ----@field set_timeout fun(self: TSParser, timeout: integer) ----@field timeout fun(self: TSParser): integer ---@field _set_logger fun(self: TSParser, lex: boolean, parse: boolean, cb: TSLoggerCallback) ---@field _logger fun(self: TSParser): TSLoggerCallback diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index f2e745ec65..6f0e377d2f 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -43,8 +43,10 @@ local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') local Range = require('vim.treesitter._range') +local hrtime = vim.uv.hrtime -local default_parse_timeout_ms = 3 +-- Parse in 3ms chunks. +local default_parse_timeout_ns = 3 * 1000000 ---@type Range2 local entire_document_range = { 0, math.huge } @@ -198,16 +200,16 @@ function LanguageTree:_set_logger() self._parser:_set_logger(log_lex, log_parse, self._logger) end ----Measure execution time of a function +---Measure execution time of a function, in nanoseconds. ---@generic R1, R2, R3 ---@param f fun(): R1, R2, R3 ---@return number, R1, R2, R3 local function tcall(f, ...) - local start = vim.uv.hrtime() + local start = hrtime() ---@diagnostic disable-next-line local r = { f(...) } --- @type number - local duration = (vim.uv.hrtime() - start) / 1000000 + local duration = hrtime() - start --- @diagnostic disable-next-line: redundant-return-value return duration, unpack(r) end @@ -388,18 +390,29 @@ function LanguageTree:_parse_regions(range, thread_state) ) then self._parser:set_included_ranges(ranges) - self._parser:set_timeout(thread_state.timeout and thread_state.timeout * 1000 or 0) -- ms -> micros - local parse_time, tree, tree_changes = - tcall(self._parser.parse, self._parser, self._trees[i], self._source, true) + local parse_time, tree, tree_changes = tcall( + self._parser.parse, + self._parser, + self._trees[i], + self._source, + true, + thread_state.timeout + ) while true do if tree then break end coroutine.yield(self._trees, false) - parse_time, tree, tree_changes = - tcall(self._parser.parse, self._parser, self._trees[i], self._source, true) + parse_time, tree, tree_changes = tcall( + self._parser.parse, + self._parser, + self._trees[i], + self._source, + true, + thread_state.timeout + ) end self:_subtract_time(thread_state, parse_time) @@ -503,7 +516,7 @@ function LanguageTree:_async_parse(range, on_parse) local buf = is_buffer_parser and vim.b[source] or nil local ct = is_buffer_parser and buf.changedtick or nil local total_parse_time = 0 - local redrawtime = vim.o.redrawtime + local redrawtime = vim.o.redrawtime * 1000000 local thread_state = {} ---@type ParserThreadState @@ -526,7 +539,7 @@ function LanguageTree:_async_parse(range, on_parse) end end - thread_state.timeout = not vim.g._ts_force_sync_parsing and default_parse_timeout_ms or nil + thread_state.timeout = not vim.g._ts_force_sync_parsing and default_parse_timeout_ns or nil local parse_time, trees, finished = tcall(parse, self, range, thread_state) total_parse_time = total_parse_time + parse_time @@ -868,35 +881,42 @@ end ---@alias vim.treesitter.languagetree.Injection table<string,table<integer,vim.treesitter.languagetree.InjectionElem>> ----@param t table<integer,vim.treesitter.languagetree.Injection> ----@param tree_index integer +---@param t vim.treesitter.languagetree.Injection ---@param pattern integer ---@param lang string ---@param combined boolean ---@param ranges Range6[] -local function add_injection(t, tree_index, pattern, lang, combined, ranges) +---@param result table<string,Range6[][]> +local function add_injection(t, pattern, lang, combined, ranges, result) if #ranges == 0 then -- Make sure not to add an empty range set as this is interpreted to mean the whole buffer. return end - -- Each tree index should be isolated from the other nodes. - if not t[tree_index] then - t[tree_index] = {} + if not result[lang] then + result[lang] = {} end - if not t[tree_index][lang] then - t[tree_index][lang] = {} + if not combined then + table.insert(result[lang], ranges) + return end - -- Key this by pattern. If combined is set to true all captures of this pattern + if not t[lang] then + t[lang] = {} + end + + -- Key this by pattern. For combined injections, all captures of this pattern -- will be parsed by treesitter as the same "source". - -- If combined is false, each "region" will be parsed as a single source. - if not t[tree_index][lang][pattern] then - t[tree_index][lang][pattern] = { combined = combined, regions = {} } + if not t[lang][pattern] then + local regions = {} + t[lang][pattern] = regions + table.insert(result[lang], regions) end - table.insert(t[tree_index][lang][pattern].regions, ranges) + for _, range in ipairs(ranges) do + table.insert(t[lang][pattern], range) + end end -- TODO(clason): replace by refactored `ts.has_parser` API (without side effects) @@ -964,19 +984,6 @@ function LanguageTree:_get_injection(match, metadata) return lang, combined, ranges end ---- Can't use vim.tbl_flatten since a range is just a table. ----@param regions Range6[][] ----@return Range6[] -local function combine_regions(regions) - local result = {} ---@type Range6[] - for _, region in ipairs(regions) do - for _, range in ipairs(region) do - result[#result + 1] = range - end - end - return result -end - --- Gets language injection regions by language. --- --- This is where most of the injection processing occurs. @@ -993,13 +1000,16 @@ function LanguageTree:_get_injections(range, thread_state) return {} end - ---@type table<integer,vim.treesitter.languagetree.Injection> - local injections = {} - local start = vim.uv.hrtime() + local start = hrtime() + + ---@type table<string,Range6[][]> + local result = {} local full_scan = range == true or self._injection_query.has_combined_injections - for index, tree in pairs(self._trees) do + for _, tree in pairs(self._trees) do + ---@type vim.treesitter.languagetree.Injection + local injections = {} local root_node = tree:root() local start_line, end_line ---@type integer, integer if full_scan then @@ -1013,38 +1023,15 @@ function LanguageTree:_get_injections(range, thread_state) do local lang, combined, ranges = self:_get_injection(match, metadata) if lang then - add_injection(injections, index, pattern, lang, combined, ranges) + add_injection(injections, pattern, lang, combined, ranges, result) else self:_log('match from injection query failed for pattern', pattern) end -- Check the current function duration against the timeout, if it exists. - local current_time = vim.uv.hrtime() - self:_subtract_time(thread_state, (current_time - start) / 1000000) - start = current_time - end - end - - ---@type table<string,Range6[][]> - local result = {} - - -- Generate a map by lang of node lists. - -- Each list is a set of ranges that should be parsed together. - for _, lang_map in pairs(injections) do - for lang, patterns in pairs(lang_map) do - if not result[lang] then - result[lang] = {} - end - - for _, entry in pairs(patterns) do - if entry.combined then - table.insert(result[lang], combine_regions(entry.regions)) - else - for _, ranges in pairs(entry.regions) do - table.insert(result[lang], ranges) - end - end - end + local current_time = hrtime() + self:_subtract_time(thread_state, current_time - start) + start = hrtime() end end diff --git a/runtime/nvim.desktop b/runtime/nvim.desktop index f9feae926d..50326fa618 100644 --- a/runtime/nvim.desktop +++ b/runtime/nvim.desktop @@ -78,7 +78,7 @@ Comment[wa]=Asspougnî des fitchîs tecses Comment[zh_CN]=编辑文本文件 Comment[zh_TW]=編輯文字檔 TryExec=nvim -Exec=nvim %F +Exec=nvim "%F" Terminal=true Type=Application Keywords=Text;editor; diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 56fbb954a7..ce4590f01b 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -1,7 +1,7 @@ " These commands create the option window. " " Maintainer: The Vim Project <https://github.com/vim/vim> -" Last Change: 2025 Mar 07 +" Last Change: 2025 Mar 28 " Former Maintainer: Bram Moolenaar <Bram@vim.org> " If there already is an option window, jump to that one. @@ -736,6 +736,8 @@ if has("insert_expand") call <SID>OptionG("ph", &ph) call <SID>AddOption("pumwidth", gettext("minimum width of the popup menu")) call <SID>OptionG("pw", &pw) + call <SID>AddOption("pummaxwidth", gettext("maximum width of the popup menu")) + call <SID>OptionG("pmw", &pmw) call <SID>AddOption("completefunc", gettext("user defined function for Insert mode completion")) call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("cfu") diff --git a/runtime/syntax/hyprlang.vim b/runtime/syntax/hyprlang.vim index cde504d9ca..081d917b4c 100644 --- a/runtime/syntax/hyprlang.vim +++ b/runtime/syntax/hyprlang.vim @@ -1,11 +1,14 @@ " Vim syntax file " Language: hyprlang " Maintainer: Luca Saccarola <github.e41mv@aleeas.com> -" Last Change: 2025 Jan 29 +" Last Change: 2025 Mar 26 if exists("b:current_syntax") finish endif +let s:cpo= &cpo +set cpo&vim + let b:current_syntax = "hyprlang" syn case ignore @@ -56,4 +59,6 @@ hi def link hyprString String hi def link hyprColor Structure hi def link hyprCommand Keyword +let &cpo = s:cpo +unlet s:cpo " vim: ts=8 sts=2 sw=2 et diff --git a/runtime/syntax/java.vim b/runtime/syntax/java.vim index 9b38ccd4dc..8dabc5e3df 100644 --- a/runtime/syntax/java.vim +++ b/runtime/syntax/java.vim @@ -3,7 +3,7 @@ " Maintainer: Aliaksei Budavei <0x000c70 AT gmail DOT com> " Former Maintainer: Claudio Fleiner <claudio@fleiner.com> " Repository: https://github.com/zzzyxwvut/java-vim.git -" Last Change: 2025 Jan 02 +" Last Change: 2025 Mar 26 " Please check ":help java.vim" for comments on some of the options " available. @@ -46,8 +46,10 @@ function! s:ff.RightConstant(x, y) abort return a:y endfunction -function! s:ff.IsRequestedPreviewFeature(n) abort - return exists("g:java_syntax_previews") && index(g:java_syntax_previews, a:n) + 1 +function! s:ff.IsAnyRequestedPreviewFeatureOf(ns) abort + return exists("g:java_syntax_previews") && + \ !empty(filter(a:ns, printf('index(%s, v:val) + 1', + \ string(g:java_syntax_previews)))) endfunction if !exists("*s:ReportOnce") @@ -108,7 +110,7 @@ syn keyword javaTypedef this super syn keyword javaOperator new instanceof syn match javaOperator "\<var\>\%(\s*(\)\@!" -if s:ff.IsRequestedPreviewFeature(476) +if s:ff.IsAnyRequestedPreviewFeatureOf([476, 494]) " Module imports can be used in any source file. syn match javaExternal "\<import\s\+module\>" contains=javaModuleImport syn keyword javaModuleImport contained module @@ -262,8 +264,12 @@ if exists("g:java_highlight_all") || exists("g:java_highlight_java") || exists(" syn keyword javaC_JavaLang Class InheritableThreadLocal ThreadLocal Enum ClassValue endif - " As of JDK 21, java.lang.Compiler is no more (deprecated in JDK 9). - syn keyword javaLangDeprecated Compiler + " As of JDK 24, SecurityManager is rendered non-functional + " (JDK-8338625). + " (Note that SecurityException and RuntimePermission are still + " not deprecated.) + " As of JDK 21, Compiler is no more (JDK-8205129). + syn keyword javaLangDeprecated Compiler SecurityManager syn cluster javaClasses add=javaC_JavaLang hi def link javaC_JavaLang javaC_Java syn keyword javaE_JavaLang AbstractMethodError ClassCircularityError ClassFormatError Error IllegalAccessError IncompatibleClassChangeError InstantiationError InternalError LinkageError NoClassDefFoundError NoSuchFieldError NoSuchMethodError OutOfMemoryError StackOverflowError ThreadDeath UnknownError UnsatisfiedLinkError VerifyError VirtualMachineError ExceptionInInitializerError UnsupportedClassVersionError AssertionError BootstrapMethodError @@ -311,7 +317,7 @@ endif exec 'syn match javaUserLabel "^\s*\<\K\k*\>\%(\<default\>\)\@' . s:ff.Peek('7', '') . '<!\s*::\@!"he=e-1' -if s:ff.IsRequestedPreviewFeature(455) +if s:ff.IsAnyRequestedPreviewFeatureOf([455, 488]) syn region javaLabelRegion transparent matchgroup=javaLabel start="\<case\>" matchgroup=NONE end=":\|->" contains=javaBoolean,javaNumber,javaCharacter,javaString,javaConstant,@javaClasses,javaGenerics,javaType,javaLabelDefault,javaLabelVarType,javaLabelWhenClause else syn region javaLabelRegion transparent matchgroup=javaLabel start="\<case\>" matchgroup=NONE end=":\|->" contains=javaLabelCastType,javaLabelNumber,javaCharacter,javaString,javaConstant,@javaClasses,javaGenerics,javaLabelDefault,javaLabelVarType,javaLabelWhenClause @@ -609,7 +615,7 @@ syn region javaString start=+"+ end=+"+ end=+$+ contains=javaSpecialChar,javaS syn region javaString start=+"""[ \t\x0c\r]*$+hs=e+1 end=+"""+he=s-1 contains=javaSpecialChar,javaSpecialError,javaTextBlockError,@Spell syn match javaTextBlockError +"""\s*"""+ -if s:ff.IsRequestedPreviewFeature(430) +if s:ff.IsAnyRequestedPreviewFeatureOf([430]) syn region javaStrTemplEmbExp contained matchgroup=javaStrTempl start="\\{" end="}" contains=TOP exec 'syn region javaStrTempl start=+\%(\.[[:space:]\n]*\)\@' . s:ff.Peek('80', '') . '<="+ end=+"+ contains=javaStrTemplEmbExp,javaSpecialChar,javaSpecialError,@Spell' exec 'syn region javaStrTempl start=+\%(\.[[:space:]\n]*\)\@' . s:ff.Peek('80', '') . '<="""[ \t\x0c\r]*$+hs=e+1 end=+"""+he=s-1 contains=javaStrTemplEmbExp,javaSpecialChar,javaSpecialError,javaTextBlockError,@Spell' @@ -688,7 +694,7 @@ if exists("g:java_highlight_debug") syn region javaDebugString contained start=+"+ end=+"+ contains=javaDebugSpecial syn region javaDebugString contained start=+"""[ \t\x0c\r]*$+hs=e+1 end=+"""+he=s-1 contains=javaDebugSpecial,javaDebugTextBlockError - if s:ff.IsRequestedPreviewFeature(430) + if s:ff.IsAnyRequestedPreviewFeatureOf([430]) " The highlight groups of java{StrTempl,Debug{,Paren,StrTempl}}\, " share one colour by default. Do not conflate unrelated parens. syn region javaDebugStrTemplEmbExp contained matchgroup=javaDebugStrTempl start="\\{" end="}" contains=javaComment,javaLineComment,javaDebug\%(Paren\)\@!.* diff --git a/runtime/syntax/lf.vim b/runtime/syntax/lf.vim index e4cf014865..3c1513fe68 100644 --- a/runtime/syntax/lf.vim +++ b/runtime/syntax/lf.vim @@ -2,15 +2,18 @@ " Language: lf file manager configuration file (lfrc) " Maintainer: Andis Sprinkis <andis@sprinkis.com> " Former Maintainer: Cameron Wright -" Former URL: https://github.com/andis-sprinkis/lf-vim -" Last Change: 13 October 2024 +" URL: https://github.com/andis-sprinkis/lf-vim +" Last Change: 28 March 2025 +" 29 Mar 2025 by Vim Project (save and restore 'cpo' setting) " " The shell syntax highlighting is configurable. See $VIMRUNTIME/doc/syntax.txt -" lf version: 32 +" lf version: 34 if exists("b:current_syntax") finish endif +let s:cpo = &cpo +set cpo&vim let b:current_syntax = "lf" @@ -33,122 +36,80 @@ syn keyword lfKeyword set setlocal cmd map cmap skipwhite "{{{ Options Keywords syn keyword lfOptions - \ quit - \ up - \ half-up - \ page-up - \ scroll-up - \ down - \ half-down - \ page-down - \ scroll-down - \ updir - \ open - \ jump-next - \ jump-prev - \ top + \ anchorfind + \ autoquit + \ borderfmt \ bottom - \ high - \ middle - \ low - \ toggle - \ invert - \ invert-below - \ unselect - \ glob-select - \ glob-unselect \ calcdirsize - \ clearmaps - \ copy - \ cut - \ paste - \ clear - \ sync - \ draw - \ redraw - \ load - \ reload - \ echo - \ echomsg - \ echoerr \ cd - \ select - \ delete - \ rename - \ source - \ push - \ read - \ shell - \ shell-pipe - \ shell-wait - \ shell-async - \ find - \ find-back - \ find-next - \ find-prev - \ search - \ search-back - \ search-next - \ search-prev - \ filter - \ setfilter - \ mark-save - \ mark-load - \ mark-remove - \ tag - \ tag-toggle - \ cmd-escape + \ cleaner + \ clear + \ clearmaps + \ cmd-capitalize-word \ cmd-complete - \ cmd-menu-complete - \ cmd-menu-complete-back - \ cmd-menu-accept + \ cmd-delete + \ cmd-delete-back + \ cmd-delete-end + \ cmd-delete-home + \ cmd-delete-unix-word + \ cmd-delete-word + \ cmd-delete-word-back + \ cmd-end \ cmd-enter - \ cmd-interrupt + \ cmd-escape \ cmd-history-next \ cmd-history-prev + \ cmd-home + \ cmd-interrupt \ cmd-left + \ cmd-lowercase-word + \ cmd-menu-accept + \ cmd-menu-complete + \ cmd-menu-complete-back \ cmd-right - \ cmd-home - \ cmd-end - \ cmd-delete - \ cmd-delete-back - \ cmd-delete-home - \ cmd-delete-end - \ cmd-delete-unix-word - \ cmd-yank \ cmd-transpose \ cmd-transpose-word + \ cmd-uppercase-word \ cmd-word \ cmd-word-back - \ cmd-delete-word - \ cmd-delete-word-back - \ cmd-capitalize-word - \ cmd-uppercase-word - \ cmd-lowercase-word - \ anchorfind - \ autoquit - \ borderfmt - \ cleaner + \ cmd-yank + \ copy \ copyfmt \ cursoractivefmt \ cursorparentfmt \ cursorpreviewfmt + \ cut \ cutfmt + \ delete \ dircache \ dircounts \ dirfirst \ dironly \ dirpreviews + \ down + \ draw \ drawbox \ dupfilefmt + \ echo + \ echoerr + \ echomsg \ errorfmt \ filesep + \ filter + \ find + \ find-back + \ find-next + \ find-prev \ findlen + \ glob-select + \ glob-unselect \ globfilter \ globsearch + \ half-down + \ half-up \ hidden \ hiddenfiles - \ hidecursorinactive + \ high \ history \ icons \ ifs @@ -159,45 +120,90 @@ syn keyword lfOptions \ info \ infotimefmtnew \ infotimefmtold + \ invert + \ invert-below + \ jump-next + \ jump-prev + \ load + \ locale + \ low + \ mark-load + \ mark-remove + \ mark-save + \ middle \ mouse \ number \ numberfmt + \ on-cd + \ on-focus-gained + \ on-focus-lost + \ on-init + \ on-quit + \ on-redraw + \ on-select + \ open + \ page-down + \ page-up + \ paste \ period + \ pre-cd \ preserve \ preview \ previewer \ promptfmt + \ push + \ quit \ ratios + \ read + \ redraw \ relativenumber + \ reload + \ rename \ reverse \ roundbox - \ ruler \ rulerfmt + \ scroll-down + \ scroll-up \ scrolloff + \ search + \ search-back + \ search-next + \ search-prev + \ select \ selectfmt \ selmode + \ setfilter \ shell + \ shell-async + \ shell-pipe + \ shell-wait \ shellflag \ shellopts + \ showbinds \ sixel \ smartcase \ smartdia \ sortby + \ source \ statfmt + \ sync \ tabstop + \ tag + \ tag-toggle \ tagfmt \ tempmarks \ timefmt + \ toggle + \ top \ truncatechar \ truncatepct + \ unselect + \ up + \ updir \ waitmsg + \ watch \ wrapscan \ wrapscroll - \ pre-cd - \ on-cd - \ on-select - \ on-redraw - \ on-quit "}}} "{{{ Special Matching @@ -234,3 +240,5 @@ hi def link lfExternalShell Normal hi def link lfExternalPatch Special hi def link lfIgnore Special "}}} +let &cpo = s:cpo +unlet s:cpo diff --git a/runtime/syntax/solidity.vim b/runtime/syntax/solidity.vim index a46d041a10..5391bba707 100644 --- a/runtime/syntax/solidity.vim +++ b/runtime/syntax/solidity.vim @@ -2,10 +2,11 @@ " Language: Solidity " Maintainer: Cothi (jiungdev@gmail.com) " Original Author: tomlion (https://github.com/tomlion/vim-solidity/blob/master/syntax/solidity.vim) -" Last Change: 2022 Sep 27 +" Last Change: 2025 Mar 25 " " Contributors: " Modified by thesis (https://github.com/thesis/vim-solidity/blob/main/indent/solidity.vim) +" Modified by S0AndS0 (https://github.com/S0AndS0/vim/blob/syntax-solidity-updates/runtime/syntax/solidity.vim) if exists("b:current_syntax") finish @@ -15,7 +16,7 @@ endif syn keyword solKeyword abstract anonymous as break calldata case catch constant constructor continue default switch revert require syn keyword solKeyword ecrecover addmod mulmod keccak256 syn keyword solKeyword delete do else emit enum external final for function if immutable import in indexed inline -syn keyword solKeyword interface internal is let match memory modifier new of payable pragma private public pure override virtual +syn keyword solKeyword interface internal is let match memory modifier new of payable pragma private public pure override virtual transient syn keyword solKeyword relocatable return returns static storage struct throw try type typeof using syn keyword solKeyword var view while @@ -158,6 +159,15 @@ hi def link solEvent Type hi def link solEventName Function hi def link solEventArgSpecial Label +" Error +syn match solError /\<error\>/ nextgroup=solErrorName,solErrorArgs skipwhite +syn match solErrorName contained /\<[a-zA-Z_$][0-9a-zA-Z_$]*/ nextgroup=solErrorArgs skipwhite +syn region solErrorArgs contained matchgroup=solFuncParens start='(' end=')' contains=solErrorArgCommas,solBuiltinType skipwhite skipempty +syn match solErrorArgCommas contained ',' + +hi def link solError Type +hi def link solErrorName Function + " Comment syn keyword solCommentTodo TODO FIXME XXX TBD contained syn match solNatSpec contained /@title\|@author\|@notice\|@dev\|@param\|@inheritdoc\|@return/ diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 7fe22190b7..29d9856b3d 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -60,7 +60,7 @@ syn case ignore syn keyword vimGroup contained Comment Constant String Character Number Boolean Float Identifier Function Statement Conditional Repeat Label Operator Keyword Exception PreProc Include Define Macro PreCondit Type StorageClass Structure Typedef Special SpecialChar Tag Delimiter SpecialComment Debug Underlined Ignore Error Todo " Default highlighting groups {{{2 -syn keyword vimHLGroup contained ErrorMsg IncSearch ModeMsg NonText StatusLine StatusLineNC EndOfBuffer VertSplit DiffText PmenuSbar TabLineSel TabLineFill Cursor lCursor QuickFixLine CursorLineSign CursorLineFold CurSearch PmenuKind PmenuKindSel PmenuMatch PmenuMatchSel PmenuExtra PmenuExtraSel ComplMatchIns Normal Directory LineNr CursorLineNr MoreMsg Question Search SpellBad SpellCap SpellRare SpellLocal PmenuThumb Pmenu PmenuSel SpecialKey Title WarningMsg WildMenu Folded FoldColumn SignColumn Visual DiffAdd DiffChange DiffDelete TabLine CursorColumn CursorLine ColorColumn MatchParen StatusLineTerm StatusLineTermNC CursorIM LineNrAbove LineNrBelow +syn keyword vimHLGroup contained ErrorMsg IncSearch ModeMsg NonText StatusLine StatusLineNC EndOfBuffer VertSplit DiffText DiffTextAdd PmenuSbar TabLineSel TabLineFill Cursor lCursor QuickFixLine CursorLineSign CursorLineFold CurSearch PmenuKind PmenuKindSel PmenuMatch PmenuMatchSel PmenuExtra PmenuExtraSel ComplMatchIns Normal Directory LineNr CursorLineNr MoreMsg Question Search SpellBad SpellCap SpellRare SpellLocal PmenuThumb Pmenu PmenuSel SpecialKey Title WarningMsg WildMenu Folded FoldColumn SignColumn Visual DiffAdd DiffChange DiffDelete TabLine CursorColumn CursorLine ColorColumn MatchParen StatusLineTerm StatusLineTermNC CursorIM LineNrAbove LineNrBelow syn match vimHLGroup contained "\<Conceal\>" syn keyword vimOnlyHLGroup contained Menu Scrollbar ToolbarButton ToolbarLine Tooltip VisualNOS syn keyword nvimHLGroup contained FloatBorder FloatFooter FloatTitle MsgSeparator NormalFloat NormalNC Substitute TermCursor VisualNC Whitespace WinBar WinBarNC WinSeparator diff --git a/src/gen/gen_eval.lua b/src/gen/gen_eval.lua index 9d2f2f7523..2f2d09485b 100644 --- a/src/gen/gen_eval.lua +++ b/src/gen/gen_eval.lua @@ -16,6 +16,7 @@ hashpipe:write([[ #include "nvim/arglist.h" #include "nvim/cmdexpand.h" #include "nvim/cmdhist.h" +#include "nvim/diff.h" #include "nvim/digraph.h" #include "nvim/eval.h" #include "nvim/eval/buffer.h" diff --git a/src/gen/gen_help_html.lua b/src/gen/gen_help_html.lua index 0d98d9e1b1..817811e857 100644 --- a/src/gen/gen_help_html.lua +++ b/src/gen/gen_help_html.lua @@ -76,6 +76,7 @@ local new_layout = { ['news.txt'] = true, ['news-0.9.txt'] = true, ['news-0.10.txt'] = true, + ['news-0.11.txt'] = true, ['nvim.txt'] = true, ['provider.txt'] = true, ['tui.txt'] = true, diff --git a/src/gen/gen_keycodes.lua b/src/gen/gen_keycodes.lua index 6fbfb5190f..584f0a3d36 100644 --- a/src/gen/gen_keycodes.lua +++ b/src/gen/gen_keycodes.lua @@ -38,43 +38,26 @@ hashorder, hashfun = hashy.hashy_hash('get_special_key_code', hashorder, functio 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 = {} - -local name_orig_idx_ = vim.deepcopy(name_orig_idx) -for i, lower_name in ipairs(hashorder) do - local orig_idx = table.remove(name_orig_idx_[lower_name], 1) - local keycode = keycode_names[orig_idx] - local key = keycode[1] - if key_orig_idx[key] == orig_idx then - key_hash_idx[key] = i - end -end -assert(vim.iter(vim.tbl_values(name_orig_idx_)):all(vim.tbl_isempty)) - 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) + int key; ///< Special key code or ascii value + bool is_alt; ///< Is an alternative name + String name; ///< Name of key } key_names_table[] = {]]) -name_orig_idx_ = vim.deepcopy(name_orig_idx) -for i, lower_name in ipairs(hashorder) do +local name_orig_idx_ = vim.deepcopy(name_orig_idx) +for _, lower_name in ipairs(hashorder) do local orig_idx = table.remove(name_orig_idx_[lower_name], 1) local keycode = keycode_names[orig_idx] local key = keycode[1] local name = keycode[2] - local pref_idx = key_hash_idx[key] names_tgt:write( - ('\n {%s, {"%s", %u}, %s},'):format( + ('\n {%s, %s, {"%s", %u}},'):format( key, + key_orig_idx[key] == orig_idx and 'false' or 'true', name, - #name, - pref_idx == i and 'NULL' or ('&key_names_table[%u].name'):format(pref_idx - 1) + #name ) ) end diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 1e33b0b62a..7c98736289 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -369,8 +369,8 @@ cleanup: /// - desc (string) optional: description (for documentation and troubleshooting). /// - callback (function|string) optional: Lua function (or Vimscript function name, if /// string) called when the event(s) is triggered. Lua callback can return a truthy -/// value (not `false` or `nil`) to delete the autocommand. Receives one argument, -/// a table with these keys: [event-args]() +/// value (not `false` or `nil`) to delete the autocommand, and receives one argument, a +/// table with these keys: [event-args]() /// - id: (number) autocommand id /// - event: (string) name of the triggered event |autocmd-events| /// - group: (number|nil) autocommand group id, if any diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index ee6571fd95..4f3ecf8efe 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -158,41 +158,40 @@ /// region is hidden by setting `eob` flag of /// 'fillchars' to a space char, and clearing the /// |hl-EndOfBuffer| region in 'winhighlight'. -/// - border: Style of (optional) window border. This can either be a string -/// or an array. The string values are the same as those described in 'winborder'. -/// If it is an array, it should have a length of eight or any divisor of -/// eight. The array will specify the eight chars building up the border -/// in a clockwise fashion starting with the top-left corner. As an -/// example, the double box style could be specified as: -/// ``` -/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. -/// ``` -/// If the number of chars are less than eight, they will be repeated. Thus -/// an ASCII border could be specified as -/// ``` -/// [ "/", "-", \"\\\\\", "|" ], -/// ``` -/// or all chars the same as -/// ``` -/// [ "x" ]. -/// ``` -/// An empty string can be used to turn off a specific border, for instance, +/// - border: (`string|string[]`) (defaults to 'winborder' option) Window border. The string form +/// accepts the same values as the 'winborder' option. The array form must have a length of +/// eight or any divisor of eight, specifying the chars that form the border in a clockwise +/// fashion starting from the top-left corner. For example, the double-box style can be +/// specified as: /// ``` -/// [ "", "", "", ">", "", "", "", "<" ] +/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. /// ``` -/// will only make vertical borders but not horizontal ones. -/// By default, `FloatBorder` highlight is used, which links to `WinSeparator` -/// when not defined. It could also be specified by character: +/// If fewer than eight chars are given, they will be repeated. An ASCII border could be +/// specified as: /// ``` -/// [ ["+", "MyCorner"], ["x", "MyBorder"] ]. +/// [ "/", "-", \"\\\\\", "|" ], /// ``` -/// - title: Title (optional) in window border, string or list. +/// Or one char for all sides: +/// ``` +/// [ "x" ]. +/// ``` +/// Empty string can be used to hide a specific border. This example will show only vertical +/// borders, not horizontal: +/// ``` +/// [ "", "", "", ">", "", "", "", "<" ] +/// ``` +/// By default, |hl-FloatBorder| highlight is used, which links to |hl-WinSeparator| when not +/// defined. Each border side can specify an optional highlight: +/// ``` +/// [ ["+", "MyCorner"], ["x", "MyBorder"] ]. +/// ``` +/// - title: (optional) Title in window border, string or list. /// List should consist of `[text, highlight]` tuples. /// If string, or a tuple lacks a highlight, the default highlight group is `FloatTitle`. /// - title_pos: Title position. Must be set with `title` option. /// Value can be one of "left", "center", or "right". /// Default is `"left"`. -/// - footer: Footer (optional) in window border, string or list. +/// - footer: (optional) Footer in window border, string or list. /// List should consist of `[text, highlight]` tuples. /// If string, or a tuple lacks a highlight, the default highlight group is `FloatFooter`. /// - footer_pos: Footer position. Must be set with `footer` option. diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index c4241eed45..f226ce9c42 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -743,18 +743,21 @@ struct file_buffer { // Stuff for diff mode. #define DB_COUNT 8 // up to four buffers can be diff'ed -// Each diffblock defines where a block of lines starts in each of the buffers -// and how many lines it occupies in that buffer. When the lines are missing -// in the buffer the df_count[] is zero. This is all counted in -// buffer lines. -// There is always at least one unchanged line in between the diffs. -// Otherwise it would have been included in the diff above or below it. -// df_lnum[] + df_count[] is the lnum below the change. When in one buffer -// lines have been inserted, in the other buffer df_lnum[] is the line below -// the insertion and df_count[] is zero. When appending lines at the end of -// the buffer, df_lnum[] is one beyond the end! -// This is using a linked list, because the number of differences is expected -// to be reasonable small. The list is sorted on lnum. +/// Each diffblock defines where a block of lines starts in each of the buffers +/// and how many lines it occupies in that buffer. When the lines are missing +/// in the buffer the df_count[] is zero. This is all counted in +/// buffer lines. +/// There is always at least one unchanged line in between the diffs (unless +/// linematch is used). Otherwise it would have been included in the diff above +/// or below it. +/// df_lnum[] + df_count[] is the lnum below the change. When in one buffer +/// lines have been inserted, in the other buffer df_lnum[] is the line below +/// the insertion and df_count[] is zero. When appending lines at the end of +/// the buffer, df_lnum[] is one beyond the end! +/// This is using a linked list, because the number of differences is expected +/// to be reasonable small. The list is sorted on lnum. +/// Each diffblock also contains a cached list of inline diff of changes within +/// the block, used for highlighting. typedef struct diffblock_S diff_T; struct diffblock_S { diff_T *df_next; @@ -762,6 +765,31 @@ struct diffblock_S { linenr_T df_count[DB_COUNT]; // nr of inserted/changed lines bool is_linematched; // has the linematch algorithm ran on this diff hunk to divide it into // smaller diff hunks? + + bool has_changes; ///< has cached list of inline changes + garray_T df_changes; ///< list of inline changes (diffline_change_T) +}; + +/// Each entry stores a single inline change within a diff block. Line numbers +/// are recorded as relative offsets, and columns are byte offsets, not +/// character counts. +/// Ranges are [start,end), with the end being exclusive. +typedef struct diffline_change_S diffline_change_T; +struct diffline_change_S { + colnr_T dc_start[DB_COUNT]; ///< byte offset of start of range in the line + colnr_T dc_end[DB_COUNT]; ///< 1 past byte offset of end of range in line + int dc_start_lnum_off[DB_COUNT]; ///< starting line offset + int dc_end_lnum_off[DB_COUNT]; ///< end line offset +}; + +/// Describes a single line's list of inline changes. Use diff_change_parse() to +/// parse this. +typedef struct diffline_S diffline_T; +struct diffline_S { + diffline_change_T *changes; + int num_changes; + int bufidx; + int lineoff; }; #define SNAP_HELP_IDX 0 diff --git a/src/nvim/change.c b/src/nvim/change.c index 192e0d9faa..cf7d5dfc4b 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -240,6 +240,7 @@ static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnum FOR_ALL_WINDOWS_IN_TAB(win, curtab) { if (win->w_buffer == buf && win->w_p_diff && diff_internal()) { curtab->tp_diff_update = true; + diff_update_line(lnum); } } diff --git a/src/nvim/diff.c b/src/nvim/diff.c index c9ca58c816..6309aa6c5e 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -29,6 +29,7 @@ #include "nvim/drawscreen.h" #include "nvim/errors.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" @@ -86,7 +87,13 @@ static bool diff_need_update = false; // ex_diffupdate needs to be called #define DIFF_CLOSE_OFF 0x400 // diffoff when closing window #define DIFF_FOLLOWWRAP 0x800 // follow the wrap option #define DIFF_LINEMATCH 0x1000 // match most similar lines within diff +#define DIFF_INLINE_NONE 0x2000 // no inline highlight +#define DIFF_INLINE_SIMPLE 0x4000 // inline highlight with simple algorithm +#define DIFF_INLINE_CHAR 0x8000 // inline highlight with character diff +#define DIFF_INLINE_WORD 0x10000 // inline highlight with word diff #define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL) +#define ALL_INLINE (DIFF_INLINE_NONE | DIFF_INLINE_SIMPLE | DIFF_INLINE_CHAR | DIFF_INLINE_WORD) +#define ALL_INLINE_DIFF (DIFF_INLINE_CHAR | DIFF_INLINE_WORD) static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF; static int diff_algorithm = 0; @@ -136,6 +143,15 @@ typedef enum { # include "diff.c.generated.h" #endif +#define FOR_ALL_DIFFBLOCKS_IN_TAB(tp, dp) \ + for ((dp) = (tp)->tp_first_diff; (dp) != NULL; (dp) = (dp)->df_next) + +static void clear_diffblock(diff_T *dp) +{ + ga_clear(&dp->df_changes); + xfree(dp); +} + /// Called when deleting or unloading a buffer: No longer make a diff with it. /// /// @param buf @@ -522,7 +538,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T /// @return The new diff block. static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp) { - diff_T *dnew = xmalloc(sizeof(*dnew)); + diff_T *dnew = xcalloc(1, sizeof(*dnew)); dnew->is_linematched = false; dnew->df_next = dp; @@ -532,13 +548,15 @@ static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp) dprev->df_next = dnew; } + dnew->has_changes = false; + ga_init(&dnew->df_changes, sizeof(diffline_change_T), 20); return dnew; } static diff_T *diff_free(tabpage_T *tp, diff_T *dprev, diff_T *dp) { diff_T *ret = dp->df_next; - xfree(dp); + clear_diffblock(dp); if (dprev == NULL) { tp->tp_first_diff = ret; @@ -763,15 +781,32 @@ static int diff_write_buffer(buf_T *buf, mmfile_t *m, linenr_T start, linenr_T e char *s = ml_get_buf(buf, lnum); if (diff_flags & DIFF_ICASE) { while (*s != NUL) { + int c; + int c_len = 1; char cbuf[MB_MAXBYTES + 1]; - // xdiff doesn't support ignoring case, fold-case the text. - int c = *s == NL ? NUL : utf_fold(utf_ptr2char(s)); + if (*s == NL) { + c = NUL; + } else { + // xdiff doesn't support ignoring case, fold-case the text. + c = utf_ptr2char(s); + c_len = utf_char2len(c); + c = utf_fold(c); + } const int orig_len = utfc_ptr2len(s); - // TODO(Bram): handle byte length difference - char *s1 = (utf_char2bytes(c, cbuf) != orig_len) ? s : cbuf; - memmove(ptr + len, s1, (size_t)orig_len); + if (utf_char2bytes(c, cbuf) != c_len) { + // TODO(Bram): handle byte length difference + // One example is Å (3 bytes) and å (2 bytes). + memmove(ptr + len, s, (size_t)orig_len); + } else { + memmove(ptr + len, cbuf, (size_t)c_len); + if (orig_len > c_len) { + // Copy remaining composing characters + memmove(ptr + len + c_len, s + c_len, (size_t)(orig_len - c_len)); + } + } + s += orig_len; len += (size_t)orig_len; } @@ -943,8 +978,7 @@ void ex_diffupdate(exarg_T *eap) } // Only use the internal method if it did not fail for one of the buffers. - diffio_T diffio; - CLEAR_FIELD(diffio); + diffio_T diffio = { 0 }; diffio.dio_internal = diff_internal(); diff_try_update(&diffio, idx_orig, eap); @@ -1639,11 +1673,6 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne if (off > 0) { dp->df_count[idx_new] += off; } - if ((dp->df_lnum[idx_new] + dp->df_count[idx_new] - 1) - > curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count) { - dp->df_count[idx_new] = curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count - - dp->df_lnum[idx_new] + 1; - } } // Adjust the size of the block to include all the lines to the @@ -1661,11 +1690,6 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne // overlap later. dp->df_count[idx_new] += -off; } - if ((dp->df_lnum[idx_new] + dp->df_count[idx_new] - 1) - > curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count) { - dp->df_count[idx_new] = curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count - - dp->df_lnum[idx_new] + 1; - } off = 0; } @@ -1682,7 +1706,7 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne while (dn != dp->df_next) { dpl = dn->df_next; - xfree(dn); + clear_diffblock(dn); dn = dpl; } } else { @@ -1716,7 +1740,7 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne static void diff_read(int idx_orig, int idx_new, diffio_T *dio) { FILE *fd = NULL; - int line_idx = 0; + int line_hunk_idx = 0; // line or hunk index diff_T *dprev = NULL; diff_T *dp = curtab->tp_first_diff; diffout_T *dout = &dio->dio_diff; @@ -1734,7 +1758,7 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio) while (true) { diffhunk_T hunk = { 0 }; bool eof = dio->dio_internal - ? extract_hunk_internal(dout, &hunk, &line_idx) + ? extract_hunk_internal(dout, &hunk, &line_hunk_idx) : extract_hunk(fd, &hunk, &diffstyle); if (eof) { @@ -1788,7 +1812,7 @@ void diff_clear(tabpage_T *tp) diff_T *next_p; for (diff_T *p = tp->tp_first_diff; p != NULL; p = next_p) { next_p = p->df_next; - xfree(p); + clear_diffblock(p); } tp->tp_first_diff = NULL; } @@ -2531,6 +2555,28 @@ int diffopt_changed(void) } else { return FAIL; } + } else if (strncmp(p, "inline:", 7) == 0) { + // Note: Keep this in sync with opt_dip_inline_values. + p += 7; + if (strncmp(p, "none", 4) == 0) { + p += 4; + diff_flags_new &= ~(ALL_INLINE); + diff_flags_new |= DIFF_INLINE_NONE; + } else if (strncmp(p, "simple", 6) == 0) { + p += 6; + diff_flags_new &= ~(ALL_INLINE); + diff_flags_new |= DIFF_INLINE_SIMPLE; + } else if (strncmp(p, "char", 4) == 0) { + p += 4; + diff_flags_new &= ~(ALL_INLINE); + diff_flags_new |= DIFF_INLINE_CHAR; + } else if (strncmp(p, "word", 4) == 0) { + p += 4; + diff_flags_new &= ~(ALL_INLINE); + diff_flags_new |= DIFF_INLINE_WORD; + } else { + return FAIL; + } } else if ((strncmp(p, "linematch:", 10) == 0) && ascii_isdigit(p[10])) { p += 10; linematch_lines_new = getdigits_int(&p, false, linematch_lines_new); @@ -2603,48 +2649,101 @@ bool diffopt_filler(void) return (diff_flags & DIFF_FILLER) != 0; } -/// Find the difference within a changed line. -/// -/// @param wp window whose current buffer to check -/// @param lnum line number to check within the buffer -/// @param startp first char of the change -/// @param endp last char of the change -/// -/// @return true if the line was added, no other buffer has it. -bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +/// Called when a line has been updated. Used for updating inline diff in Insert +/// mode without waiting for global diff update later. +void diff_update_line(linenr_T lnum) { - // Make a copy of the line, the next ml_get() will invalidate it. - char *line_org = xstrdup(ml_get_buf(wp->w_buffer, lnum)); + if (!(diff_flags & ALL_INLINE_DIFF)) { + // We only care if we are doing inline-diff where we cache the diff results + return; + } - int idx = diff_buf_idx(wp->w_buffer, curtab); + int idx = diff_buf_idx(curbuf, curtab); if (idx == DB_COUNT) { - // cannot happen - xfree(line_org); - return false; + return; } - - // search for a change that includes "lnum" in the list of diffblocks. diff_T *dp; - for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { + FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp) { if (lnum <= dp->df_lnum[idx] + dp->df_count[idx]) { break; } } - if (dp != NULL && dp->is_linematched) { - while (dp && dp->df_next - && lnum == dp->df_count[idx] + dp->df_lnum[idx] - && dp->df_next->df_lnum[idx] == lnum) { - dp = dp->df_next; - } + + // clear the inline change cache as it's invalid + if (dp != NULL) { + dp->has_changes = false; + dp->df_changes.ga_len = 0; } +} + +/// used for simple inline diff algorithm +static diffline_change_T simple_diffline_change; - if ((dp == NULL) || (diff_check_sanity(curtab, dp) == FAIL)) { - xfree(line_org); +/// Parse a diffline struct and returns the [start,end] byte offsets +/// +/// Returns true if this change was added, no other buffer has it. +bool diff_change_parse(diffline_T *diffline, diffline_change_T *change, int *change_start, + int *change_end) +{ + if (change->dc_start_lnum_off[diffline->bufidx] < diffline->lineoff) { + *change_start = 0; + } else { + *change_start = change->dc_start[diffline->bufidx]; + } + if (change->dc_end_lnum_off[diffline->bufidx] > diffline->lineoff) { + *change_end = INT_MAX; + } else { + *change_end = change->dc_end[diffline->bufidx]; + } + if (change == &simple_diffline_change) { + // This is what we returned from simple inline diff. We always consider + // the range to be changed, rather than added for now. return false; } + // Find out whether this is an addition. Note that for multi buffer diff, + // to tell whether lines are additions we check whether all the other diff + // lines are identical (in diff_check_with_linestatus). If so, we mark them + // as add. We don't do that for inline diff here for simplicity. + for (int i = 0; i < DB_COUNT; i++) { + if (i == diffline->bufidx) { + continue; + } + if (change->dc_start[i] != change->dc_end[i] + || change->dc_end_lnum_off[i] != change->dc_start_lnum_off[i]) { + return false; + } + } + return true; +} + +/// Find the difference within a changed line and returns [startp,endp] byte +/// positions. Performs a simple algorithm by finding a single range in the +/// middle. +/// +/// If diffopt has DIFF_INLINE_NONE set, then this will only calculate the return +/// value (added or changed), but startp/endp will not be calculated. +/// +/// @param wp window whose current buffer to check +/// @param lnum line number to check within the buffer +/// @param startp first char of the change +/// @param endp last char of the change +/// +/// @return true if the line was added, no other buffer has it. +static bool diff_find_change_simple(win_T *wp, linenr_T lnum, const diff_T *dp, int idx, + int *startp, int *endp) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + char *line_org; + if (diff_flags & DIFF_INLINE_NONE) { + // We only care about the return value, not the actual string comparisons. + line_org = NULL; + } else { + // Make a copy of the line, the next ml_get() will invalidate it. + line_org = xstrdup(ml_get_buf(wp->w_buffer, lnum)); + } + int si_org; int si_new; int ei_org; @@ -2659,6 +2758,10 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) continue; } added = false; + if (diff_flags & DIFF_INLINE_NONE) { + break; // early terminate as we only care about the return value + } + char *line_new = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + off); // Search for start of difference @@ -2737,6 +2840,465 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) return added; } +/// Mapping used for mapping from temporary mmfile created for inline diff back +/// to original buffer's line/col. +typedef struct { + colnr_T byte_start; + colnr_T num_bytes; + int lineoff; +} linemap_entry_T; + +/// Refine inline character-wise diff blocks to create a more human readable +/// highlight. Otherwise a naive diff under existing algorithms tends to create +/// a messy output with lots of small gaps. +/// It does this by merging adjacent long diff blocks if they are only separated +/// by a couple characters. +/// These are done by heuristics and can be further tuned. +static void diff_refine_inline_char_highlight(diff_T *dp_orig, garray_T *linemap, int idx1) +{ + // Perform multiple passes so that newly merged blocks will now be long + // enough which may cause other previously unmerged gaps to be merged as + // well. + int pass = 1; + do { + bool has_unmerged_gaps = false; + bool has_merged_gaps = false; + diff_T *dp = dp_orig; + while (dp != NULL && dp->df_next != NULL) { + // Only use first buffer to calculate the gap because the gap is + // unchanged text, which would be the same in all buffers. + if (dp->df_lnum[idx1] + dp->df_count[idx1] - 1 >= linemap[idx1].ga_len + || dp->df_next->df_lnum[idx1] - 1 >= linemap[idx1].ga_len) { + dp = dp->df_next; + continue; + } + + // If the gap occurs over different lines, don't consider it + linemap_entry_T *entry1 = + &((linemap_entry_T *)linemap[idx1].ga_data)[dp->df_lnum[idx1] + + dp->df_count[idx1] - 1]; + linemap_entry_T *entry2 = + &((linemap_entry_T *)linemap[idx1].ga_data)[dp->df_next->df_lnum[idx1] - 1]; + if (entry1->lineoff != entry2->lineoff) { + dp = dp->df_next; + continue; + } + + linenr_T gap = dp->df_next->df_lnum[idx1] - (dp->df_lnum[idx1] + dp->df_count[idx1]); + if (gap <= 3) { + linenr_T max_df_count = 0; + for (int i = 0; i < DB_COUNT; i++) { + max_df_count = MAX(max_df_count, dp->df_count[i] + dp->df_next->df_count[i]); + } + + if (max_df_count >= gap * 4) { + // Merge current block with the next one. Don't advance the + // pointer so we try the same merged block against the next + // one. + for (int i = 0; i < DB_COUNT; i++) { + dp->df_count[i] = dp->df_next->df_lnum[i] + + dp->df_next->df_count[i] - dp->df_lnum[i]; + } + diff_T *dp_next = dp->df_next; + dp->df_next = dp_next->df_next; + clear_diffblock(dp_next); + has_merged_gaps = true; + continue; + } else { + has_unmerged_gaps = true; + } + } + dp = dp->df_next; + } + if (!has_unmerged_gaps || !has_merged_gaps) { + break; + } + } while (pass++ < 4); // use limited number of passes to avoid excessive looping +} + +/// Find the inline difference within a diff block among differnt buffers. Do +/// this by splitting each block's content into characters or words, and then +/// use internal xdiff to calculate the per-character/word diff. The result is +/// stored in dp instead of returned by the function. +static void diff_find_change_inline_diff(diff_T *dp) +{ + const int save_diff_algorithm = diff_algorithm; + + diffio_T dio = { 0 }; + ga_init(&dio.dio_diff.dout_ga, sizeof(char *), 1000); + + // inline diff only supports internal algo + dio.dio_internal = true; + + // always use indent-heuristics to slide diff splits along + // whitespace + diff_algorithm |= XDF_INDENT_HEURISTIC; + + // diff_read() has an implicit dependency on curtab->tp_first_diff + diff_T *orig_diff = curtab->tp_first_diff; + curtab->tp_first_diff = NULL; + + garray_T linemap[DB_COUNT]; + garray_T file1_str; + garray_T file2_str; + + // Buffers to populate mmfile 1/2 that would be passed to xdiff as memory + // files. Use a grow array as it is not obvious how much exact space we + // need. + ga_init(&file1_str, 1, 1024); + ga_init(&file2_str, 1, 1024); + + // Line map to map from generated mmfiles' line numbers back to original + // diff blocks' locations. Need this even for char diff because not all + // characters are 1-byte long / ASCII. + for (int i = 0; i < DB_COUNT; i++) { + ga_init(&linemap[i], sizeof(linemap_entry_T), 128); + } + + int file1_idx = -1; + for (int i = 0; i < DB_COUNT; i++) { + dio.dio_diff.dout_ga.ga_len = 0; + + buf_T *buf = curtab->tp_diffbuf[i]; + if (buf == NULL || buf->b_ml.ml_mfp == NULL) { + continue; // skip buffer that isn't loaded + } + if (dp->df_count[i] == 0) { + continue; // skip buffer that don't have any texts in this block + } + if (file1_idx == -1) { + file1_idx = i; + } + + garray_T *curstr = (file1_idx != i) ? &file2_str : &file1_str; + + linenr_T numlines = 0; + curstr->ga_len = 0; + + // Split each line into chars/words and populate fake file buffer as + // newline-delimited tokens as that's what xdiff requires. + for (int off = 0; off < dp->df_count[i]; off++) { + char *curline = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + off); + + bool in_keyword = false; + + // iwhiteeol support vars + bool last_white = false; + int eol_ga_len = -1; + int eol_linemap_len = -1; + int eol_numlines = -1; + + char *s = curline; + while (*s != NUL) { + // Always use the first buffer's 'iskeyword' to have a consistent diff + bool new_in_keyword = false; + if (diff_flags & DIFF_INLINE_WORD) { + new_in_keyword = vim_iswordp_buf(s, curtab->tp_diffbuf[file1_idx]); + } + if (in_keyword && !new_in_keyword) { + ga_append(curstr, NL); + numlines++; + } + + if (ascii_iswhite(*s)) { + if (diff_flags & DIFF_IWHITEALL) { + in_keyword = false; + s = skipwhite(s); + continue; + } else if ((diff_flags & DIFF_IWHITEEOL) || (diff_flags & DIFF_IWHITE)) { + if (!last_white) { + eol_ga_len = curstr->ga_len; + eol_linemap_len = linemap[i].ga_len; + eol_numlines = numlines; + last_white = true; + } + } + } else { + if ((diff_flags & DIFF_IWHITEEOL) || (diff_flags & DIFF_IWHITE)) { + last_white = false; + eol_ga_len = -1; + eol_linemap_len = -1; + eol_numlines = -1; + } + } + + int char_len = 1; + if (*s == NL) { + // NL is internal substitute for NUL + ga_append(curstr, NUL); + } else { + char_len = utfc_ptr2len(s); + + if (ascii_iswhite(*s) && (diff_flags & DIFF_IWHITE)) { + // Treat the entire white space span as a single char. + char_len = (int)(skipwhite(s) - s); + } + + if (diff_flags & DIFF_ICASE) { + // xdiff doesn't support ignoring case, fold-case the text manually. + int c = utf_ptr2char(s); + int c_len = utf_char2len(c); + c = utf_fold(c); + char cbuf[MB_MAXBYTES + 1]; + int c_fold_len = utf_char2bytes(c, cbuf); + ga_concat_len(curstr, cbuf, (size_t)c_fold_len); + if (char_len > c_len) { + // There may be remaining composing characters. Write those back in. + // Composing characters don't need case folding. + ga_concat_len(curstr, s + c_len, (size_t)(char_len - c_len)); + } + } else { + ga_concat_len(curstr, s, (size_t)char_len); + } + } + + if (!new_in_keyword) { + ga_append(curstr, NL); + numlines++; + } + + if (!new_in_keyword || (new_in_keyword && !in_keyword)) { + // create a new mapping entry from the xdiff mmfile back to + // original line/col. + linemap_entry_T linemap_entry = { + .lineoff = off, + .byte_start = (colnr_T)(s - curline), + .num_bytes = char_len, + }; + GA_APPEND(linemap_entry_T, &linemap[i], linemap_entry); + } else { + // Still inside a keyword. Just increment byte count but + // don't make a new entry. + // linemap always has at least one entry here + ((linemap_entry_T *)linemap[i].ga_data)[linemap[i].ga_len - 1].num_bytes += char_len; + } + + in_keyword = new_in_keyword; + s += char_len; + } + if (in_keyword) { + ga_append(curstr, NL); + numlines++; + } + + if ((diff_flags & DIFF_IWHITEEOL) || (diff_flags & DIFF_IWHITE)) { + // Need to trim trailing whitespace. Do this simply by + // resetting arrays back to before we encountered them. + if (eol_ga_len != -1) { + curstr->ga_len = eol_ga_len; + linemap[i].ga_len = eol_linemap_len; + numlines = eol_numlines; + } + } + + if (!(diff_flags & DIFF_IWHITEALL)) { + // Add an empty line token mapped to the end-of-line in the + // original file. This helps diff newline differences among + // files, which will be visualized when using 'list' as the eol + // listchar will be highlighted. + ga_append(curstr, NL); + numlines++; + + linemap_entry_T linemap_entry = { + .lineoff = off, + .byte_start = (colnr_T)(s - curline), + .num_bytes = sizeof(NL), + }; + GA_APPEND(linemap_entry_T, &linemap[i], linemap_entry); + } + } + + if (file1_idx != i) { + dio.dio_new.din_mmfile.ptr = (char *)curstr->ga_data; + dio.dio_new.din_mmfile.size = curstr->ga_len; + } else { + dio.dio_orig.din_mmfile.ptr = (char *)curstr->ga_data; + dio.dio_orig.din_mmfile.size = curstr->ga_len; + } + if (file1_idx != i) { + // Perform diff with first file and read the results + int diff_status = diff_file_internal(&dio); + if (diff_status == FAIL) { + goto done; + } + + diff_read(0, i, &dio); + clear_diffout(&dio.dio_diff); + } + } + diff_T *new_diff = curtab->tp_first_diff; + + if (diff_flags & DIFF_INLINE_CHAR && file1_idx != -1) { + diff_refine_inline_char_highlight(new_diff, linemap, file1_idx); + } + + // After the diff, use the linemap to obtain the original line/col of the + // changes and cache them in dp. + dp->df_changes.ga_len = 0; // this should already be zero + for (; new_diff != NULL; new_diff = new_diff->df_next) { + diffline_change_T change = { 0 }; + for (int i = 0; i < DB_COUNT; i++) { + if (new_diff->df_lnum[i] == 0) { + continue; + } + linenr_T diff_lnum = new_diff->df_lnum[i] - 1; // use zero-index + linenr_T diff_lnum_end = diff_lnum + new_diff->df_count[i]; + + if (diff_lnum >= linemap[i].ga_len) { + change.dc_start[i] = MAXCOL; + change.dc_start_lnum_off[i] = INT_MAX; + } else { + change.dc_start[i] = ((linemap_entry_T *)linemap[i].ga_data)[diff_lnum].byte_start; + change.dc_start_lnum_off[i] = ((linemap_entry_T *)linemap[i].ga_data)[diff_lnum].lineoff; + } + + if (diff_lnum == diff_lnum_end) { + change.dc_end[i] = change.dc_start[i]; + change.dc_end_lnum_off[i] = change.dc_start_lnum_off[i]; + } else if (diff_lnum_end - 1 >= linemap[i].ga_len) { + change.dc_end[i] = MAXCOL; + change.dc_end_lnum_off[i] = INT_MAX; + } else { + change.dc_end[i] = ((linemap_entry_T *)linemap[i].ga_data)[diff_lnum_end - 1].byte_start + + ((linemap_entry_T *)linemap[i].ga_data)[diff_lnum_end - 1].num_bytes; + change.dc_end_lnum_off[i] = ((linemap_entry_T *)linemap[i].ga_data)[diff_lnum_end - + 1].lineoff; + } + } + GA_APPEND(diffline_change_T, &dp->df_changes, change); + } + +done: + diff_algorithm = save_diff_algorithm; + + dp->has_changes = true; + + diff_clear(curtab); + curtab->tp_first_diff = orig_diff; + + ga_clear(&file1_str); + ga_clear(&file2_str); + // No need to clear dio.dio_orig/dio_new because they were referencing + // strings that are now cleared. + clear_diffout(&dio.dio_diff); + for (int i = 0; i < DB_COUNT; i++) { + ga_clear(&linemap[i]); + } +} + +/// Find the difference within a changed line. +/// Returns true if the line was added, no other buffer has it. +bool diff_find_change(win_T *wp, linenr_T lnum, diffline_T *diffline) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + int idx = diff_buf_idx(wp->w_buffer, curtab); + if (idx == DB_COUNT) { // cannot happen + return false; + } + + // search for a change that includes "lnum" in the list of diffblocks. + diff_T *dp; + FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp) { + if (lnum <= dp->df_lnum[idx] + dp->df_count[idx]) { + break; + } + } + if (dp && dp->is_linematched) { + while (dp && dp->df_next + && lnum == dp->df_count[idx] + dp->df_lnum[idx] + && dp->df_next->df_lnum[idx] == lnum) { + dp = dp->df_next; + } + } + if (dp == NULL || diff_check_sanity(curtab, dp) == FAIL) { + return false; + } + + int off = lnum - dp->df_lnum[idx]; + + if (!(diff_flags & ALL_INLINE_DIFF)) { + // Use simple algorithm + int change_start = MAXCOL; // first col of changed area + int change_end = -1; // last col of changed area + + int ret = diff_find_change_simple(wp, lnum, dp, idx, &change_start, &change_end); + + // convert from inclusive end to exclusive end per diffline's contract + change_end += 1; + + // Create a mock diffline struct. We always only have one so no need to + // allocate memory. + CLEAR_FIELD(simple_diffline_change); + diffline->changes = &simple_diffline_change; + diffline->num_changes = 1; + diffline->bufidx = idx; + diffline->lineoff = lnum - dp->df_lnum[idx]; + + simple_diffline_change.dc_start[idx] = change_start; + simple_diffline_change.dc_end[idx] = change_end; + simple_diffline_change.dc_start_lnum_off[idx] = off; + simple_diffline_change.dc_end_lnum_off[idx] = off; + return ret; + } + + // Use inline diff algorithm. + // The diff changes are usually cached so we check that first. + if (!dp->has_changes) { + diff_find_change_inline_diff(dp); + } + + garray_T *changes = &dp->df_changes; + + // Use linear search to find the first change for this line. We could + // optimize this to use binary search, but there should usually be a + // limited number of inline changes per diff block, and limited number of + // diff blocks shown on screen, so it is not necessary. + int num_changes = 0; + int change_idx = 0; + diffline->changes = NULL; + for (change_idx = 0; change_idx < changes->ga_len; change_idx++) { + diffline_change_T *change = + &((diffline_change_T *)dp->df_changes.ga_data)[change_idx]; + if (change->dc_end_lnum_off[idx] < off) { + continue; + } + if (change->dc_start_lnum_off[idx] > off) { + break; + } + if (diffline->changes == NULL) { + diffline->changes = change; + } + num_changes++; + } + diffline->num_changes = num_changes; + diffline->bufidx = idx; + diffline->lineoff = off; + + // Detect simple cases of added lines in the end within a diff block. This + // has to be the last change of this diff block, and all other buffers are + // considering this to be an addition past their last line. Other scenarios + // will be considered a changed line instead. + bool added = false; + if (num_changes == 1 && change_idx == dp->df_changes.ga_len) { + added = true; + for (int i = 0; i < DB_COUNT; i++) { + if (idx == i) { + continue; + } + if (curtab->tp_diffbuf[i] == NULL) { + continue; + } + diffline_change_T *change = + &((diffline_change_T *)dp->df_changes.ga_data)[dp->df_changes.ga_len - 1]; + if (change->dc_start_lnum_off[i] != INT_MAX) { + added = false; + break; + } + } + } + return added; +} + /// Check that line "lnum" is not close to a diff block, this line should /// be in a fold. /// @@ -3488,3 +4050,93 @@ static int xdiff_out(int start_a, int count_a, int start_b, int count_b, void *p })); return 0; } + +/// "diff_filler()" function +void f_diff_filler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = MAX(0, diff_check(curwin, tv_get_lnum(argvars))); +} + +/// "diff_hlID()" function +void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + static linenr_T prev_lnum = 0; + static varnumber_T changedtick = 0; + static int fnum = 0; + static int prev_diff_flags = 0; + static int change_start = 0; + static int change_end = 0; + static hlf_T hlID = (hlf_T)0; + + diffline_T diffline = { 0 }; + // Remember the results if using simple since it's recalculated per + // call. Otherwise just call diff_find_change() every time since + // internally the result is cached interally. + const bool cache_results = !(diff_flags & ALL_INLINE_DIFF); + + linenr_T lnum = tv_get_lnum(argvars); + if (lnum < 0) { // ignore type error in {lnum} arg + lnum = 0; + } + if (!cache_results + || lnum != prev_lnum + || changedtick != buf_get_changedtick(curbuf) + || fnum != curbuf->b_fnum + || diff_flags != prev_diff_flags) { + // New line, buffer, change: need to get the values. + int linestatus = 0; + int filler_lines = diff_check_with_linestatus(curwin, lnum, &linestatus); + if (filler_lines < 0 || linestatus < 0) { + if (filler_lines == -1 || linestatus == -1) { + change_start = MAXCOL; + change_end = -1; + if (diff_find_change(curwin, lnum, &diffline)) { + hlID = HLF_ADD; // added line + } else { + hlID = HLF_CHD; // changed line + if (diffline.num_changes > 0 && cache_results) { + change_start = diffline.changes[0].dc_start[diffline.bufidx]; + change_end = diffline.changes[0].dc_end[diffline.bufidx]; + } + } + } else { + hlID = HLF_ADD; // added line + } + } else { + hlID = (hlf_T)0; + } + + if (cache_results) { + prev_lnum = lnum; + changedtick = buf_get_changedtick(curbuf); + fnum = curbuf->b_fnum; + prev_diff_flags = diff_flags; + } + } + + if (hlID == HLF_CHD || hlID == HLF_TXD) { + int col = (int)tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}. + if (cache_results) { + if (col >= change_start && col < change_end) { + hlID = HLF_TXD; // Changed text. + } else { + hlID = HLF_CHD; // Changed line. + } + } else { + hlID = HLF_CHD; + for (int i = 0; i < diffline.num_changes; i++) { + bool added = diff_change_parse(&diffline, &diffline.changes[i], + &change_start, &change_end); + if (col >= change_start && col < change_end) { + hlID = added ? HLF_TXA : HLF_TXD; + break; + } + if (col < change_start) { + // the remaining changes are past this column and not relevant + break; + } + } + } + } + rettv->vval.v_number = hlID; +} diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 16e3a95121..43d9f67b5c 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -844,6 +844,18 @@ static void apply_cursorline_highlight(win_T *wp, winlinevars_T *wlv) } } +static void set_line_attr_for_diff(win_T *wp, winlinevars_T *wlv) +{ + wlv->line_attr = win_hl_attr(wp, (int)wlv->diff_hlf); + // Overlay CursorLine onto diff-mode highlight. + if (wlv->cul_attr) { + wlv->line_attr = 0 != wlv->line_attr_lowprio // Low-priority CursorLine + ? hl_combine_attr(hl_combine_attr(wlv->cul_attr, wlv->line_attr), + hl_get_underline()) + : hl_combine_attr(wlv->line_attr, wlv->cul_attr); + } +} + /// Checks if there is more inline virtual text that need to be drawn. static bool has_more_inline_virt(winlinevars_T *wlv, ptrdiff_t v) { @@ -1259,14 +1271,28 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b int linestatus = 0; wlv.filler_lines = diff_check_with_linestatus(wp, lnum, &linestatus); + diffline_T line_changes = { 0 }; + int change_index = -1; if (wlv.filler_lines < 0 || linestatus < 0) { if (wlv.filler_lines == -1 || linestatus == -1) { - if (diff_find_change(wp, lnum, &change_start, &change_end)) { - wlv.diff_hlf = HLF_ADD; // added line - } else if (change_start == 0) { - wlv.diff_hlf = HLF_TXD; // changed text + if (diff_find_change(wp, lnum, &line_changes)) { + wlv.diff_hlf = HLF_ADD; // added line + } else if (line_changes.num_changes > 0) { + bool added = diff_change_parse(&line_changes, &line_changes.changes[0], + &change_start, &change_end); + if (change_start == 0) { + if (added) { + wlv.diff_hlf = HLF_TXA; // added text on changed line + } else { + wlv.diff_hlf = HLF_TXD; // changed text on changed line + } + } else { + wlv.diff_hlf = HLF_CHD; // unchanged text on changed line + } + change_index = 0; } else { - wlv.diff_hlf = HLF_CHD; // changed line + wlv.diff_hlf = HLF_CHD; // changed line + change_index = 0; } } else { wlv.diff_hlf = HLF_ADD; // added line @@ -1846,24 +1872,32 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b } if (wlv.diff_hlf != (hlf_T)0) { + if (line_changes.num_changes > 0 + && change_index >= 0 + && change_index < line_changes.num_changes - 1) { + if (ptr - line + >= line_changes.changes[change_index + 1].dc_start[line_changes.bufidx]) { + change_index += 1; + } + } + bool added = false; + if (line_changes.num_changes > 0 && change_index >= 0 + && change_index < line_changes.num_changes) { + added = diff_change_parse(&line_changes, &line_changes.changes[change_index], + &change_start, &change_end); + } // When there is extra text (eg: virtual text) it gets the // diff highlighting for the line, but not for changed text. if (wlv.diff_hlf == HLF_CHD && ptr - line >= change_start && wlv.n_extra == 0) { - wlv.diff_hlf = HLF_TXD; // changed text - } - if (wlv.diff_hlf == HLF_TXD && ((ptr - line > change_end && wlv.n_extra == 0) - || (wlv.n_extra > 0 && wlv.extra_for_extmark))) { - wlv.diff_hlf = HLF_CHD; // changed line + wlv.diff_hlf = added ? HLF_TXA : HLF_TXD; // added/changed text } - wlv.line_attr = win_hl_attr(wp, (int)wlv.diff_hlf); - // Overlay CursorLine onto diff-mode highlight. - if (wlv.cul_attr) { - wlv.line_attr = 0 != wlv.line_attr_lowprio // Low-priority CursorLine - ? hl_combine_attr(hl_combine_attr(wlv.cul_attr, wlv.line_attr), - hl_get_underline()) - : hl_combine_attr(wlv.line_attr, wlv.cul_attr); + if ((wlv.diff_hlf == HLF_TXD || wlv.diff_hlf == HLF_TXA) + && ((ptr - line >= change_end && wlv.n_extra == 0) + || (wlv.n_extra > 0 && wlv.extra_for_extmark))) { + wlv.diff_hlf = HLF_CHD; // changed line } + set_line_attr_for_diff(wp, &wlv); } // Decide which of the highlight attributes to use. @@ -2727,8 +2761,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b const int cuc_attr = win_hl_attr(wp, HLF_CUC); const int mc_attr = win_hl_attr(wp, HLF_MC); - if (wlv.diff_hlf == HLF_TXD) { + if (wlv.diff_hlf == HLF_TXD || wlv.diff_hlf == HLF_TXA) { wlv.diff_hlf = HLF_CHD; + set_line_attr_for_diff(wp, &wlv); } const int diff_attr = wlv.diff_hlf != 0 diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 4c1b756ea1..2905d51657 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -1700,6 +1700,16 @@ static void win_update(win_T *wp) } } + // Below logic compares wp->w_topline against wp->w_lines[0].wl_lnum, + // which may point to a line below wp->w_topline if it is concealed; + // incurring scrolling even though wp->w_topline is still the same. + // Compare against an adjusted topline instead: + linenr_T topline_conceal = wp->w_topline; + while (decor_conceal_line(wp, topline_conceal - 1, false)) { + topline_conceal++; + hasFolding(wp, topline_conceal, NULL, &topline_conceal); + } + // If there are no changes on the screen that require a complete redraw, // handle three cases: // 1: we are off the top of the screen by a few lines: scroll down @@ -1712,12 +1722,12 @@ static void win_update(win_T *wp) if (mod_top != 0 && wp->w_topline == mod_top && (!wp->w_lines[0].wl_valid - || wp->w_topline == wp->w_lines[0].wl_lnum)) { + || topline_conceal == wp->w_lines[0].wl_lnum)) { // w_topline is the first changed line and window is not scrolled, // the scrolling from changed lines will be done further down. } else if (wp->w_lines[0].wl_valid - && (wp->w_topline < wp->w_lines[0].wl_lnum - || (wp->w_topline == wp->w_lines[0].wl_lnum + && (topline_conceal < wp->w_lines[0].wl_lnum + || (topline_conceal == wp->w_lines[0].wl_lnum && wp->w_topfill > wp->w_old_topfill))) { // New topline is above old topline: May scroll down. int j; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index f2001b6f6f..4eeba5e38d 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -2774,7 +2774,7 @@ char *get_last_insert_save(void) char *s = xmemdupz(insert.data, insert.size); if (insert.size > 0 && s[insert.size - 1] == ESC) { // remain trailing ESC - s[insert.size - 1] = NUL; + s[--insert.size] = NUL; } return s; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ee5dd25e93..3f73074333 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -8530,7 +8530,7 @@ void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) typval_T eval_call_provider(char *provider, char *method, list_T *arguments, bool discard) { if (!eval_has_provider(provider, false)) { - semsg("E319: No \"%s\" provider found. Run \":checkhealth provider\"", + semsg("E319: No \"%s\" provider found. Run \":checkhealth vim.provider\"", provider); return (typval_T){ .v_type = VAR_NUMBER, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index edb77778a3..25252cdfde 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -34,7 +34,6 @@ #include "nvim/cmdexpand_defs.h" #include "nvim/context.h" #include "nvim/cursor.h" -#include "nvim/diff.h" #include "nvim/edit.h" #include "nvim/errors.h" #include "nvim/eval.h" @@ -1300,63 +1299,6 @@ static void f_did_filetype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr rettv->vval.v_number = curbuf->b_did_filetype; } -/// "diff_filler()" function -static void f_diff_filler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = MAX(0, diff_check(curwin, tv_get_lnum(argvars))); -} - -/// "diff_hlID()" function -static void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - linenr_T lnum = tv_get_lnum(argvars); - static linenr_T prev_lnum = 0; - static varnumber_T changedtick = 0; - static int fnum = 0; - static int change_start = 0; - static int change_end = 0; - static hlf_T hlID = (hlf_T)0; - - if (lnum < 0) { // ignore type error in {lnum} arg - lnum = 0; - } - if (lnum != prev_lnum - || changedtick != buf_get_changedtick(curbuf) - || fnum != curbuf->b_fnum) { - // New line, buffer, change: need to get the values. - int linestatus = 0; - int filler_lines = diff_check_with_linestatus(curwin, lnum, &linestatus); - if (filler_lines < 0 || linestatus < 0) { - if (filler_lines == -1 || linestatus == -1) { - change_start = MAXCOL; - change_end = -1; - if (diff_find_change(curwin, lnum, &change_start, &change_end)) { - hlID = HLF_ADD; // added line - } else { - hlID = HLF_CHD; // changed line - } - } else { - hlID = HLF_ADD; // added line - } - } else { - hlID = (hlf_T)0; - } - prev_lnum = lnum; - changedtick = buf_get_changedtick(curbuf); - fnum = curbuf->b_fnum; - } - - if (hlID == HLF_CHD || hlID == HLF_TXD) { - int col = (int)tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}. - if (col >= change_start && col <= change_end) { - hlID = HLF_TXD; // Changed text. - } else { - hlID = HLF_CHD; // Changed line. - } - } - rettv->vval.v_number = hlID; -} - /// "empty({expr})" function static void f_empty(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 6c5cca5d5c..746dc15c71 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -407,6 +407,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) int count = 0; // line number count bool did_inc = false; // incremented RedrawingDisabled int block_indent = -1; // indent for ext_cmdline block event + char *block_line = NULL; // block_line for ext_cmdline block event int retval = OK; cstack_T cstack = { // conditional stack .cs_idx = -1, @@ -577,7 +578,9 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) int indent = cstack.cs_idx < 0 ? 0 : (cstack.cs_idx + 1) * 2; if (count >= 1 && getline_equal(fgetline, cookie, getexline)) { if (ui_has(kUICmdline)) { - ui_ext_cmdline_block_append((size_t)MAX(0, block_indent), last_cmdline); + char *line = block_line == last_cmdline ? "" : last_cmdline; + ui_ext_cmdline_block_append((size_t)MAX(0, block_indent), line); + block_line = last_cmdline; block_indent = indent; } else if (count == 1) { // Need to set msg_didout for the first line after an ":if", @@ -683,8 +686,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // If the command was typed, remember it for the ':' register. // Do this AFTER executing the command to make :@: work. - if (getline_equal(fgetline, cookie, getexline) - && new_last_cmdline != NULL) { + if (getline_equal(fgetline, cookie, getexline) && new_last_cmdline != NULL) { xfree(last_cmdline); last_cmdline = new_last_cmdline; new_last_cmdline = NULL; diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h index a89d778474..03853c2ddb 100644 --- a/src/nvim/highlight.h +++ b/src/nvim/highlight.h @@ -45,6 +45,7 @@ EXTERN const char *hlf_names[] INIT( = { [HLF_CHD] = "DiffChange", [HLF_DED] = "DiffDelete", [HLF_TXD] = "DiffText", + [HLF_TXA] = "DiffTextAdd", [HLF_SC] = "SignColumn", [HLF_CONCEAL] = "Conceal", [HLF_SPB] = "SpellBad", diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index cbbc28311f..dba69fcf71 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -93,6 +93,7 @@ typedef enum { HLF_CHD, ///< Changed diff line HLF_DED, ///< Deleted diff line HLF_TXD, ///< Text Changed in diff line + HLF_TXA, ///< Text Added in changed diff line HLF_SC, ///< Sign column HLF_CONCEAL, ///< Concealed text HLF_SPB, ///< SpellBad diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index 901d2c84bc..2ecd3b9af7 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -159,6 +159,7 @@ static const char *highlight_init_both[] = { "default link CursorIM Cursor", "default link CursorLineFold FoldColumn", "default link CursorLineSign SignColumn", + "default link DiffTextAdd DiffText", "default link EndOfBuffer NonText", "default link FloatBorder NormalFloat", "default link FloatFooter FloatTitle", diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c index 9cb2321eee..778682a881 100644 --- a/src/nvim/keycodes.c +++ b/src/nvim/keycodes.c @@ -338,10 +338,7 @@ char *get_special_key_name(int c, int modifiers) } } } else { // use name of special key - const String *s = key_names_table[table_idx].pref_name != NULL - ? key_names_table[table_idx].pref_name - : &key_names_table[table_idx].name; - + const String *s = &key_names_table[table_idx].name; if ((int)s->size + idx + 2 <= MAX_KEY_NAME_LEN) { STRCPY(string + idx, s->data); idx += (int)s->size; @@ -593,7 +590,7 @@ static int extract_modifiers(int key, int *modp, const bool simplify, bool *cons int find_special_key_in_table(int c) { for (int i = 0; i < (int)ARRAY_SIZE(key_names_table); i++) { - if (c == key_names_table[i].key) { + if (c == key_names_table[i].key && !key_names_table[i].is_alt) { return i; } } diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index a346bf5963..5d3599920e 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -15,6 +15,8 @@ #include <tree_sitter/api.h> #include <uv.h> +#include "nvim/os/time.h" + #ifdef HAVE_WASMTIME # include <wasm.h> @@ -52,6 +54,11 @@ typedef struct { TSTree *tree; } TSLuaTree; +typedef struct { + uint64_t parse_start_time; + uint64_t timeout_threshold_ns; +} TSLuaParserCallbackPayload; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/treesitter.c.generated.h" #endif @@ -362,8 +369,6 @@ static struct luaL_Reg parser_meta[] = { { "reset", parser_reset }, { "set_included_ranges", parser_set_ranges }, { "included_ranges", parser_get_ranges }, - { "set_timeout", parser_set_timeout }, - { "timeout", parser_get_timeout }, { "_set_logger", parser_set_logger }, { "_logger", parser_get_logger }, { NULL, NULL } @@ -487,6 +492,13 @@ static void push_ranges(lua_State *L, const TSRange *ranges, const size_t length } } +static bool on_parser_progress(TSParseState *state) +{ + TSLuaParserCallbackPayload *payload = state->payload; + uint64_t parse_time = os_hrtime() - payload->parse_start_time; + return parse_time >= payload->timeout_threshold_ns; +} + static int parser_parse(lua_State *L) { TSParser *p = parser_check(L, 1); @@ -524,7 +536,17 @@ static int parser_parse(lua_State *L) } input = (TSInput){ (void *)buf, input_cb, TSInputEncodingUTF8, NULL }; - new_tree = ts_parser_parse(p, old_tree, input); + if (!lua_isnil(L, 5)) { + uint64_t timeout_ns = (uint64_t)lua_tointeger(L, 5); + TSLuaParserCallbackPayload payload = + (TSLuaParserCallbackPayload){ .parse_start_time = os_hrtime(), + .timeout_threshold_ns = timeout_ns }; + TSParseOptions parse_options = { .payload = &payload, + .progress_callback = on_parser_progress }; + new_tree = ts_parser_parse_with_options(p, old_tree, input, parse_options); + } else { + new_tree = ts_parser_parse(p, old_tree, input); + } break; @@ -534,12 +556,11 @@ static int parser_parse(lua_State *L) bool include_bytes = (lua_gettop(L) >= 4) && lua_toboolean(L, 4); - // Sometimes parsing fails (timeout, or wrong parser ABI) - // In those case, just return an error. if (!new_tree) { - if (ts_parser_timeout_micros(p) == 0) { - // No timeout set, must have had an error - return luaL_error(L, "An error occurred when parsing."); + // Sometimes parsing fails (no language was set, or it was set to one with an incompatible ABI) + // In those cases, just return an error. + if (!ts_parser_language(p)) { + return luaL_error(L, "Language was unset, or has an incompatible ABI."); } return 0; } @@ -670,26 +691,6 @@ static int parser_get_ranges(lua_State *L) return 1; } -static int parser_set_timeout(lua_State *L) -{ - TSParser *p = parser_check(L, 1); - - if (lua_gettop(L) < 2) { - luaL_error(L, "integer expected"); - } - - uint32_t timeout = (uint32_t)luaL_checkinteger(L, 2); - ts_parser_set_timeout_micros(p, timeout); - return 0; -} - -static int parser_get_timeout(lua_State *L) -{ - TSParser *p = parser_check(L, 1); - lua_pushinteger(L, (lua_Integer)ts_parser_timeout_micros(p)); - return 1; -} - static void logger_cb(void *payload, TSLogType logtype, const char *s) { TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)payload; diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index ab9d68f911..3dc66f7dba 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -2805,7 +2805,8 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod goto fail_and_free; case 5: api_set_error(err, kErrorTypeException, - "E227: mapping already exists for %s", parsed_args.lhs); + is_abbrev ? e_abbreviation_already_exists_for_str + : e_mapping_already_exists_for_str, lhs.data); goto fail_and_free; default: assert(false && "Unrecognized return code!"); diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index b0ab235f6b..0003d88543 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -655,6 +655,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) bool in_winbar = (jump_flags & MOUSE_WINBAR); bool in_statuscol = (jump_flags & MOUSE_STATUSCOL); bool in_status_line = (jump_flags & IN_STATUS_LINE); + bool in_global_statusline = in_status_line && global_stl_height() > 0; bool in_sep_line = (jump_flags & IN_SEP_LINE); if ((in_winbar || in_status_line || in_statuscol) && is_click) { @@ -671,7 +672,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) : in_winbar ? wp->w_winbar_click_defs : wp->w_statuscol_click_defs; - if (in_status_line && global_stl_height() > 0) { + if (in_global_statusline) { // global statusline is displayed for the current window, // and spans the whole screen. click_defs = curwin->w_status_click_defs; @@ -681,7 +682,11 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) if (in_statuscol && wp->w_p_rl) { click_col = wp->w_width_inner - click_col - 1; } - if (in_statuscol && click_col >= (int)wp->w_statuscol_click_defs_size) { + + if ((in_statuscol && click_col >= (int)wp->w_statuscol_click_defs_size) + || (in_status_line + && click_col >= + (int)(in_global_statusline ? curwin : wp)->w_status_click_defs_size)) { return false; } diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index 2e5698870f..e624ab80ef 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -13,11 +13,11 @@ // option_vars.h: definition of global variables for settable options #define HIGHLIGHT_INIT \ - "8:SpecialKey,~:EndOfBuffer,z:TermCursor,@:NonText,d:Directory,e:ErrorMsg," \ - "i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow," \ - "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold,r:Question,s:StatusLine,S:StatusLineNC," \ - "c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn," \ - "A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn,-:Conceal,B:SpellBad,P:SpellCap," \ + "8:SpecialKey,~:EndOfBuffer,z:TermCursor,@:NonText,d:Directory,e:ErrorMsg,i:IncSearch,l:Search," \ + "y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow,N:CursorLineNr," \ + "G:CursorLineSign,O:CursorLineFold,r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title," \ + "v:Visual,V:VisualNOS,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange," \ + "D:DiffDelete,T:DiffText,E:DiffTextAdd,>:SignColumn,-:Conceal,B:SpellBad,P:SpellCap," \ "R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,k:PmenuMatch,<:PmenuMatchSel,[:PmenuKind," \ "]:PmenuKindSel,{:PmenuExtra,}:PmenuExtraSel,x:PmenuSbar,X:PmenuThumb,*:TabLine,#:TabLineSel," \ "_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn,q:QuickFixLine,z:StatusLineTerm," \ @@ -306,6 +306,7 @@ EXTERN char *p_csl; ///< 'completeslash' EXTERN OptInt p_pb; ///< 'pumblend' EXTERN OptInt p_ph; ///< 'pumheight' EXTERN OptInt p_pw; ///< 'pumwidth' +EXTERN OptInt p_pmw; ///< 'pummaxwidth' EXTERN char *p_com; ///< 'comments' EXTERN char *p_cpo; ///< 'cpoptions' EXTERN char *p_debug; ///< 'debug' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index f566582c0c..f261abf30e 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2188,7 +2188,7 @@ local options = { { abbreviation = 'dip', cb = 'did_set_diffopt', - defaults = 'internal,filler,closeoff,linematch:40', + defaults = 'internal,filler,closeoff,inline:simple,linematch:40', -- Keep this in sync with diffopt_changed(). values = { 'filler', @@ -2207,6 +2207,7 @@ local options = { 'internal', 'indent-heuristic', { 'algorithm:', { 'myers', 'minimal', 'patience', 'histogram' } }, + { 'inline:', { 'none', 'simple', 'char', 'word' } }, 'linematch:', }, deny_duplicates = true, @@ -2272,6 +2273,21 @@ local options = { Use the indent heuristic for the internal diff library. + inline:{text} Highlight inline differences within a change. + See |view-diffs|. Supported values are: + + none Do not perform inline highlighting. + simple Highlight from first different + character to the last one in each + line. This is the default if no + `inline:` value is set. + char Use internal diff to perform a + character-wise diff and highlight the + difference. + word Use internal diff to perform a + |word|-wise diff and highlight the + difference. + internal Use the internal diff library. This is ignored when 'diffexpr' is set. *E960* When running out of memory when writing a @@ -6410,6 +6426,21 @@ local options = { varname = 'p_ph', }, { + abbreviation = 'pmw', + defaults = 0, + desc = [=[ + Maximum width for the popup menu (|ins-completion-menu|). When zero, + there is no maximum width limit, otherwise the popup menu will never be + wider than this value. Truncated text will be indicated by "..." at the + end. Takes precedence over 'pumwidth'. + ]=], + full_name = 'pummaxwidth', + scope = { 'global' }, + short_desc = N_('maximum width of the popup menu'), + type = 'number', + varname = 'p_pmw', + }, + { abbreviation = 'pw', defaults = 15, desc = [=[ diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index c6cc7af8cd..905656ccc9 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -1018,6 +1018,16 @@ int expand_set_diffopt(optexpand_T *args, int *numMatches, char ***matches) numMatches, matches); } + // Within "inline:", we have a subgroup of possible options. + const size_t inline_len = strlen("inline:"); + if (xp->xp_pattern - args->oe_set_arg >= (int)inline_len + && strncmp(xp->xp_pattern - inline_len, "inline:", inline_len) == 0) { + return expand_set_opt_string(args, + opt_dip_inline_values, + ARRAY_SIZE(opt_dip_inline_values) - 1, + numMatches, + matches); + } return FAIL; } diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index d60d0b3e55..e711611d67 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -1211,7 +1211,6 @@ static void read_input(StringBuilder *buf) size_t lplen = (size_t)ml_get_len(lnum); while (true) { - lplen -= written; if (lplen == 0) { len = 0; } else if (lp[written] == NL) { @@ -1220,11 +1219,11 @@ static void read_input(StringBuilder *buf) kv_push(*buf, NUL); } else { char *s = vim_strchr(lp + written, NL); - len = s == NULL ? lplen : (size_t)(s - (lp + written)); + len = s == NULL ? lplen - written : (size_t)(s - (lp + written)); kv_concat_len(*buf, lp + written, len); } - if (len == lplen) { + if (len == lplen - written) { // Finished a line, add a NL, unless this line should not have one. if (lnum != curbuf->b_op_end.lnum || (!curbuf->b_p_bin && curbuf->b_p_fixeol) diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index b1c6e02449..2e5d08e5ae 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -199,6 +199,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } int def_width = (int)p_pw; + if (p_pmw > 0 && def_width > p_pmw) { + def_width = (int)p_pmw; + } win_T *pvwin = NULL; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -307,6 +310,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i pum_compute_size(); int max_width = pum_base_width; + if (p_pmw > 0 && max_width > p_pmw) { + max_width = (int)p_pmw; + } // if there are more items than room we need a scrollbar if (pum_height < size) { @@ -339,6 +345,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i if (pum_width > content_width && pum_width > p_pw) { // Reduce width to fit item pum_width = MAX(content_width, (int)p_pw); + if (p_pmw > 0 && pum_width > p_pmw) { + pum_width = (int)p_pmw; + } } else if (((cursor_col - min_col > p_pw || cursor_col - min_col > max_width) && !pum_rl) || (pum_rl && (cursor_col < max_col - p_pw @@ -365,6 +374,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i if (pum_width < p_pw) { pum_width = (int)p_pw; + if (p_pmw > 0 && pum_width > p_pmw) { + pum_width = (int)p_pmw; + } if (pum_rl) { if (pum_width > pum_col - min_col) { pum_width = pum_col - min_col; @@ -376,6 +388,11 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } } else if (pum_width > content_width && pum_width > p_pw) { pum_width = MAX(content_width, (int)p_pw); + if (p_pmw > 0 && pum_width > p_pmw) { + pum_width = (int)p_pmw; + } + } else if (p_pmw > 0 && pum_width > p_pmw) { + pum_width = (int)p_pmw; } } } else if (max_col - min_col < def_width) { @@ -386,11 +403,17 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i pum_col = min_col; } pum_width = max_col - min_col - 1; + if (p_pmw > 0 && pum_width > p_pmw) { + pum_width = (int)p_pmw; + } } else { if (max_width > p_pw) { // truncate max_width = (int)p_pw; } + if (p_pmw > 0 && max_width > p_pmw) { + max_width = (int)p_pmw; + } if (pum_rl) { pum_col = min_col + max_width - 1; } else { @@ -611,6 +634,8 @@ void pum_redraw(void) thumb_pos = (pum_first * (pum_height - thumb_height) + scroll_range / 2) / scroll_range; } + const int ellipsis_width = 3; + for (int i = 0; i < pum_height; i++) { int idx = i + pum_first; const hlf_T *const hlfs = (idx == pum_selected) ? hlfsSel : hlfsNorm; @@ -633,6 +658,7 @@ void pum_redraw(void) // Do this 3 times and order from p_cia int grid_col = col_off; int totwidth = 0; + bool need_ellipsis = false; int order[3]; int items_width_array[3] = { pum_base_width, pum_kind_width, pum_extra_width }; pum_align_order(order); @@ -684,15 +710,19 @@ void pum_redraw(void) if (pum_rl) { char *rt = reverse_text(st); char *rt_start = rt; - int cells = vim_strsize(rt); + int cells = (int)mb_string2cells(rt); + if (p_pmw > ellipsis_width && pum_width == p_pmw + && grid_col - cells < col_off - pum_width) { + need_ellipsis = true; + } - if (cells > pum_width) { + if (grid_col - cells < col_off - pum_width) { do { cells -= utf_ptr2cells(rt); MB_PTR_ADV(rt); - } while (cells > pum_width); + } while (grid_col - cells < col_off - pum_width); - if (cells < pum_width) { + if (grid_col - cells > col_off - pum_width) { // Most left character requires 2-cells but only 1 cell is available on // screen. Put a '<' on the left of the pum item. *(--rt) = '<'; @@ -710,10 +740,16 @@ void pum_redraw(void) xfree(st); grid_col -= width; } else { + int cells = (int)mb_string2cells(st); + if (p_pmw > ellipsis_width && pum_width == p_pmw + && grid_col + cells > col_off + pum_width) { + need_ellipsis = true; + } + if (attrs == NULL) { grid_line_puts(grid_col, st, -1, attr); } else { - pum_grid_puts_with_attrs(grid_col, vim_strsize(st), st, -1, attrs); + pum_grid_puts_with_attrs(grid_col, cells, st, -1, attrs); } xfree(st); @@ -772,9 +808,24 @@ void pum_redraw(void) } if (pum_rl) { - grid_line_fill(col_off - pum_width + 1, grid_col + 1, schar_from_ascii(' '), orig_attr); + const int lcol = col_off - pum_width + 1; + grid_line_fill(lcol, grid_col + 1, schar_from_ascii(' '), orig_attr); + if (need_ellipsis) { + bool over_wide = pum_width > ellipsis_width && linebuf_char[lcol + ellipsis_width] == NUL; + grid_line_fill(lcol, lcol + ellipsis_width, schar_from_ascii('.'), orig_attr); + if (over_wide) { + grid_line_put_schar(lcol + ellipsis_width, schar_from_ascii(' '), orig_attr); + } + } } else { - grid_line_fill(grid_col, col_off + pum_width, schar_from_ascii(' '), orig_attr); + const int rcol = col_off + pum_width; + grid_line_fill(grid_col, rcol, schar_from_ascii(' '), orig_attr); + if (need_ellipsis) { + if (pum_width > ellipsis_width && linebuf_char[rcol - ellipsis_width] == NUL) { + grid_line_put_schar(rcol - ellipsis_width - 1, schar_from_ascii(' '), orig_attr); + } + grid_line_fill(rcol - ellipsis_width, rcol, schar_from_ascii('.'), orig_attr); + } } if (pum_scrollbar > 0) { diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index de9a7e580f..7a8d963dee 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -367,6 +367,8 @@ static const char e_nfa_regexp_missing_value_in_chr[] static const char e_atom_engine_must_be_at_start_of_pattern[] = N_("E1281: Atom '\\%%#=%c' must be at the start of the pattern"); static const char e_substitute_nesting_too_deep[] = N_("E1290: substitute nesting too deep"); +static const char e_unicode_val_too_large[] + = N_("E1541: Value too large, max Unicode codepoint is U+10FFFF"); #define NOT_MULTI 0 #define MULTI_ONE 1 @@ -4796,6 +4798,11 @@ collection: || *regparse == 'u' || *regparse == 'U') { startc = coll_get_char(); + // max UTF-8 Codepoint is U+10FFFF, + // but allow values until INT_MAX + if (startc == INT_MAX) { + EMSG_RET_NULL(_(e_unicode_val_too_large)); + } if (startc == 0) { regc(0x0a); } else { @@ -5548,12 +5555,15 @@ static int coll_get_char(void) case 'U': nr = gethexchrs(8); break; } - if (nr < 0 || nr > INT_MAX) { + if (nr < 0) { // If getting the number fails be backwards compatible: the character // is a backslash. regparse--; nr = '\\'; } + if (nr > INT_MAX) { + nr = INT_MAX; + } return (int)nr; } @@ -10565,6 +10575,11 @@ collection: || *regparse == 'U') { // TODO(RE): This needs more testing startc = coll_get_char(); + // max UTF-8 Codepoint is U+10FFFF, + // but allow values until INT_MAX + if (startc == INT_MAX) { + EMSG_RET_FAIL(_(e_unicode_val_too_large)); + } got_coll_char = true; MB_PTR_BACK(old_regparse, regparse); } else { diff --git a/src/nvim/vterm/state.c b/src/nvim/vterm/state.c index 0e43107347..3b7b5e2997 100644 --- a/src/nvim/vterm/state.c +++ b/src/nvim/vterm/state.c @@ -1948,28 +1948,26 @@ static int on_osc(int command, VTermStringFragment frag, void *user) case 0: settermprop_string(state, VTERM_PROP_ICONNAME, frag); settermprop_string(state, VTERM_PROP_TITLE, frag); - return 1; + break; case 1: settermprop_string(state, VTERM_PROP_ICONNAME, frag); - return 1; + break; case 2: settermprop_string(state, VTERM_PROP_TITLE, frag); - return 1; + break; case 52: if (state->selection.callbacks) { osc_selection(state, frag); } + break; + } - return 1; - - default: - if (state->fallbacks && state->fallbacks->osc) { - if ((*state->fallbacks->osc)(command, frag, state->fbdata)) { - return 1; - } + if (state->fallbacks && state->fallbacks->osc) { + if ((*state->fallbacks->osc)(command, frag, state->fbdata)) { + return 1; } } diff --git a/src/nvim/window.c b/src/nvim/window.c index dd351a6af7..916c193469 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -196,6 +196,10 @@ win_T *swbuf_goto_win_with_buf(buf_T *buf) return wp; } +// 'cmdheight' value explicitly set by the user: window commands are allowed to +// resize the topframe to values higher than this minimum, but not lower. +static OptInt min_set_ch = 1; + /// all CTRL-W window commands are handled here, called from normal_cmd(). /// /// @param xchar extra char from ":wincmd gx" or NUL @@ -513,7 +517,7 @@ newwindow: // set current window height case Ctrl__: case '_': - win_setheight(Prenum ? Prenum : Rows - 1); + win_setheight(Prenum ? Prenum : Rows - (int)min_set_ch); break; // increase current window width @@ -3505,10 +3509,6 @@ static bool is_bottom_win(win_T *wp) return true; } -// 'cmdheight' value explicitly set by the user: window commands are allowed to -// resize the topframe to values higher than this minimum, but not lower. -static OptInt min_set_ch = 1; - /// Set a new height for a frame. Recursively sets the height for contained /// frames and windows. Caller must take care of positions. /// diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index 995711507f..acaccee4e5 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -803,11 +803,23 @@ describe('nvim_set_keymap, nvim_del_keymap', function() it('throws appropriate error messages when setting <unique> maps', function() api.nvim_set_keymap('l', 'lhs', 'rhs', {}) eq( - 'E227: mapping already exists for lhs', + 'E227: Mapping already exists for lhs', pcall_err(api.nvim_set_keymap, 'l', 'lhs', 'rhs', { unique = true }) ) -- different mapmode, no error should be thrown api.nvim_set_keymap('t', 'lhs', 'rhs', { unique = true }) + + api.nvim_set_keymap('n', '<tab>', 'rhs', {}) + eq( + 'E227: Mapping already exists for <tab>', + pcall_err(api.nvim_set_keymap, 'n', '<tab>', 'rhs', { unique = true }) + ) + + api.nvim_set_keymap('ia', 'lhs', 'rhs', {}) + eq( + 'E226: Abbreviation already exists for lhs', + pcall_err(api.nvim_set_keymap, 'ia', 'lhs', 'rhs', { unique = true }) + ) end) it('can set <expr> mappings whose RHS change dynamically', function() diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 3632774bff..58634460c8 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -86,7 +86,7 @@ describe('vim.health', function() end) describe(':checkhealth', function() - it('functions report_*() render correctly', function() + it('report_xx() renders correctly', function() command('checkhealth full_render') n.expect([[ @@ -94,15 +94,15 @@ describe('vim.health', function() test_plug.full_render: require("test_plug.full_render.health").check() report 1 ~ - - OK life is fine - - WARNING no what installed + - ✅ OK life is fine + - ⚠️ WARNING no what installed - ADVICE: - pip what - make what report 2 ~ - stuff is stable - - ERROR why no hardcopy + - ❌ ERROR why no hardcopy - ADVICE: - :help |:hardcopy| - :help |:TOhtml| @@ -117,25 +117,25 @@ describe('vim.health', function() test_plug: require("test_plug.health").check() report 1 ~ - - OK everything is fine + - ✅ OK everything is fine report 2 ~ - - OK nothing to see here + - ✅ OK nothing to see here ============================================================================== test_plug.success1: require("test_plug.success1.health").check() report 1 ~ - - OK everything is fine + - ✅ OK everything is fine report 2 ~ - - OK nothing to see here + - ✅ OK nothing to see here ============================================================================== test_plug.success2: require("test_plug.success2.health").check() another 1 ~ - - OK ok + - ✅ OK ok ]]) end) @@ -147,10 +147,10 @@ describe('vim.health', function() test_plug.submodule: require("test_plug.submodule.health").check() report 1 ~ - - OK everything is fine + - ✅ OK everything is fine report 2 ~ - - OK nothing to see here + - ✅ OK nothing to see here ]]) end) @@ -161,7 +161,7 @@ describe('vim.health', function() ============================================================================== test_plug.submodule_empty: require("test_plug.submodule_empty.health").check() - - ERROR The healthcheck report for "test_plug.submodule_empty" plugin is empty. + - ❌ ERROR The healthcheck report for "test_plug.submodule_empty" plugin is empty. ]]) end) @@ -182,13 +182,13 @@ describe('vim.health', function() {Bar: }| {h1:foo: }| | - - {Error:ERROR} No healthcheck found for "foo" plugin. | + - ❌ {Error:ERROR} No healthcheck found for "foo" plugin. | | {Bar: }| {h1:test_plug.success1: require("test_pl}| | {h2:report 1} | - - {Ok:OK} everything is fine | + - ✅ {Ok:OK} everything is fine | | ]], } @@ -202,11 +202,11 @@ describe('vim.health', function() ============================================================================== non_existent_healthcheck: - - ERROR No healthcheck found for "non_existent_healthcheck" plugin. + - ❌ ERROR No healthcheck found for "non_existent_healthcheck" plugin. ]]) end) - it('does not use vim.health as a healtcheck', function() + it('does not use vim.health as a healthcheck', function() -- vim.health is not a healthcheck command('checkhealth vim') n.expect([[ @@ -221,7 +221,7 @@ describe('vim.health', function() test_plug.lua: require("test_plug.lua.health").check() nested lua/ directory ~ - - OK everything is ok + - ✅ OK everything is ok ]]) end) @@ -239,19 +239,19 @@ describe('vim.health', function() nest: require("nest.health").check() healthy pack ~ - - OK healthy ok + - ✅ OK healthy ok ]]) end) end) end) -describe(':checkhealth provider', function() +describe(':checkhealth vim.provider', function() it("works correctly with a wrongly configured 'shell'", function() clear() command([[set shell=echo\ WRONG!!!]]) command('let g:loaded_perl_provider = 0') command('let g:loaded_python3_provider = 0') - command('checkhealth provider') + command('checkhealth vim.provider') eq(nil, string.match(curbuf_contents(), 'WRONG!!!')) end) end) @@ -285,10 +285,10 @@ describe(':checkhealth window', function() {h1:require("test_plug.success1.health").check()} | | {h2:report 1} | - - {32:OK} everything is fine | + - ✅ {32:OK} everything is fine | | {h2:report 2} | - - {32:OK} nothing to see here | + - ✅ {32:OK} nothing to see here | ## grid 3 | ]], @@ -328,12 +328,14 @@ describe(':checkhealth window', function() {h1:success1.health").check()}| | {h2:report 1} | - - {32:OK} everything is fine | + - ✅ {32:OK} everything is | + fine | | {h2:report 2} | - - {32:OK} nothing to see here | + - ✅ {32:OK} nothing to see | + here | | - {1:~ }|*3 + {1:~ }| ]]):format( left and '[4:-------------------------]│[2:------------------------]|*19' or '[2:------------------------]│[4:-------------------------]|*19', @@ -385,10 +387,10 @@ describe(':checkhealth window', function() require("test_plug.success1.health").check() | | report 1 | - - OK everything is fine | + - ✅ OK everything is fine | | report 2 | - - OK nothing to see here | + - ✅ OK nothing to see here | | ]]):format( top diff --git a/test/functional/plugin/lsp/inlay_hint_spec.lua b/test/functional/plugin/lsp/inlay_hint_spec.lua index e410a54c31..3ff190fb55 100644 --- a/test/functional/plugin/lsp/inlay_hint_spec.lua +++ b/test/functional/plugin/lsp/inlay_hint_spec.lua @@ -297,6 +297,50 @@ int main() { end) ) end) + + it('does not request hints from lsp when disabled', function() + exec_lua(function() + _G.server2 = _G._create_server({ + capabilities = { + inlayHintProvider = true, + }, + handlers = { + ['textDocument/inlayHint'] = function(_, _, callback) + _G.got_inlay_hint_request = true + callback(nil, {}) + end, + }, + }) + _G.client2 = vim.lsp.start({ name = 'dummy2', cmd = _G.server2.cmd }) + end) + + local function was_request_sent() + return exec_lua(function() + return _G.got_inlay_hint_request == true + end) + end + + eq(false, was_request_sent()) + + exec_lua(function() + vim.lsp.inlay_hint.get() + end) + + eq(false, was_request_sent()) + + exec_lua(function() + vim.lsp.inlay_hint.enable(false, { bufnr = bufnr }) + vim.lsp.inlay_hint.get() + end) + + eq(false, was_request_sent()) + + exec_lua(function() + vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) + end) + + eq(true, was_request_sent()) + end) end) end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 614c49a41f..856c086add 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -6414,5 +6414,40 @@ describe('LSP', function() filetypes = true, }, 'cannot start foo due to config error: .* filetypes: expected table, got boolean') end) + + it('does not start without workspace if workspace_required=true', function() + exec_lua(create_server_definition) + + local tmp1 = t.tmpname(true) + + eq( + { workspace_required = false }, + exec_lua(function() + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) + + local ws_required = { cmd = server.cmd, workspace_required = true, filetypes = { 'foo' } } + local ws_not_required = vim.deepcopy(ws_required) + ws_not_required.workspace_required = false + + vim.lsp.config('ws_required', ws_required) + vim.lsp.config('ws_not_required', ws_not_required) + vim.lsp.enable('ws_required') + vim.lsp.enable('ws_not_required') + + vim.cmd.edit(assert(tmp1)) + vim.bo.filetype = 'foo' + + local clients = vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() }) + assert(1 == #clients) + return { workspace_required = clients[1].config.workspace_required } + end) + ) + end) end) end) diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index b348f77b38..510eacb958 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -575,22 +575,22 @@ int x = INT_MAX; eq(5, exec_lua('return #parser:children().c:trees()')) eq({ { 0, 0, 7, 0 }, -- root tree + { 1, 26, 1, 63 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) + { 2, 29, 2, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) { 3, 14, 3, 17 }, -- VALUE 123 { 4, 15, 4, 18 }, -- VALUE1 123 { 5, 15, 5, 18 }, -- VALUE2 123 - { 1, 26, 1, 63 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) - { 2, 29, 2, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) }, get_ranges()) n.feed('ggo<esc>') eq(5, exec_lua('return #parser:children().c:trees()')) eq({ { 0, 0, 8, 0 }, -- root tree + { 2, 26, 2, 63 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) + { 3, 29, 3, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) { 4, 14, 4, 17 }, -- VALUE 123 { 5, 15, 5, 18 }, -- VALUE1 123 { 6, 15, 6, 18 }, -- VALUE2 123 - { 2, 26, 2, 63 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) - { 3, 29, 3, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) }, get_ranges()) end) end) @@ -613,11 +613,11 @@ int x = INT_MAX; eq(2, exec_lua('return #parser:children().c:trees()')) eq({ { 0, 0, 7, 0 }, -- root tree + { 1, 26, 2, 66 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) + -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) { 3, 14, 5, 18 }, -- VALUE 123 -- VALUE1 123 -- VALUE2 123 - { 1, 26, 2, 66 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) - -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) }, get_ranges()) n.feed('ggo<esc>') @@ -625,11 +625,11 @@ int x = INT_MAX; eq(2, exec_lua('return #parser:children().c:trees()')) eq({ { 0, 0, 8, 0 }, -- root tree - { 4, 14, 6, 18 }, -- VALUE 123 - -- VALUE1 123 - -- VALUE2 123 { 2, 26, 3, 66 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) + -- VALUE 123 + { 4, 14, 6, 18 }, -- VALUE1 123 + -- VALUE2 123 }, get_ranges()) n.feed('7ggI//<esc>') @@ -638,10 +638,10 @@ int x = INT_MAX; eq(2, exec_lua('return #parser:children().c:trees()')) eq({ { 0, 0, 8, 0 }, -- root tree - { 4, 14, 5, 18 }, -- VALUE 123 - -- VALUE1 123 { 2, 26, 3, 66 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) + -- VALUE 123 + { 4, 14, 5, 18 }, -- VALUE1 123 }, get_ranges()) end) @@ -794,22 +794,22 @@ int x = INT_MAX; eq(5, exec_lua('return #parser:children().c:trees()')) eq({ { 0, 0, 7, 0 }, -- root tree + { 1, 26, 1, 63 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) + { 2, 29, 2, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) { 3, 14, 3, 17 }, -- VALUE 123 { 4, 15, 4, 18 }, -- VALUE1 123 { 5, 15, 5, 18 }, -- VALUE2 123 - { 1, 26, 1, 63 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) - { 2, 29, 2, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) }, get_ranges()) n.feed('ggo<esc>') eq(5, exec_lua('return #parser:children().c:trees()')) eq({ { 0, 0, 8, 0 }, -- root tree + { 2, 26, 2, 63 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) + { 3, 29, 3, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) { 4, 14, 4, 17 }, -- VALUE 123 { 5, 15, 5, 18 }, -- VALUE1 123 { 6, 15, 6, 18 }, -- VALUE2 123 - { 2, 26, 2, 63 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) - { 3, 29, 3, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) }, get_ranges()) end) end) @@ -831,11 +831,11 @@ int x = INT_MAX; eq('table', exec_lua('return type(parser:children().c)')) eq({ { 0, 0, 7, 0 }, -- root tree + { 1, 26, 1, 63 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) + { 2, 29, 2, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) { 3, 16, 3, 16 }, -- VALUE 123 { 4, 17, 4, 17 }, -- VALUE1 123 { 5, 17, 5, 17 }, -- VALUE2 123 - { 1, 26, 1, 63 }, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) - { 2, 29, 2, 66 }, -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) }, get_ranges()) end) it('should list all directives', function() diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index cb9d978a0f..fb44c2ab52 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -858,7 +858,7 @@ local function test_cmdline(linegrid) cmdline = { { content = { { '' } }, - hl_id = 242, + hl_id = 243, pos = 0, prompt = 'Prompt:', }, @@ -886,7 +886,7 @@ local function test_cmdline(linegrid) }, cmdline_block = { { { 'if 1' } } }, }) - feed(':let x = 1<CR>') + feed('let x = 1<CR>') screen:expect({ grid = s1, cmdline = { @@ -897,20 +897,34 @@ local function test_cmdline(linegrid) pos = 0, }, }, - cmdline_block = { { { 'if 1' } }, { { ' :let x = 1' } } }, + cmdline_block = { { { 'if 1' } }, { { ' let x = 1' } } }, }) - feed(':endif') + feed('<CR>') + eq('let x = 1', eval('@:')) screen:expect({ grid = s1, cmdline = { { - content = { { ':endif' } }, + content = { { '' } }, firstc = ':', indent = 2, - pos = 6, + pos = 0, + }, + }, + cmdline_block = { { { 'if 1' } }, { { ' let x = 1' } }, { { ' ' } } }, + }) + feed('endif') + screen:expect({ + grid = s1, + cmdline = { + { + content = { { 'endif' } }, + firstc = ':', + indent = 2, + pos = 5, }, }, - cmdline_block = { { { 'if 1' } }, { { ' :let x = 1' } } }, + cmdline_block = { { { 'if 1' } }, { { ' let x = 1' } }, { { ' ' } } }, }) feed('<CR>') screen:expect({ @@ -1352,6 +1366,12 @@ describe('cmdline height', function() -- cmdheight unchanged. eq(1, eval('&cmdheight')) end) + + it('not increased to 0 from 1 with wincmd _', function() + command('set cmdheight=0 laststatus=0') + command('wincmd _') + eq(0, eval('&cmdheight')) + end) end) describe('cmdheight=0', function() diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index ff304db54c..087ae79eb1 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -258,11 +258,11 @@ describe('ui/cursor', function() end end if m.hl_id then - m.hl_id = 65 + m.hl_id = 66 m.attr = { background = Screen.colors.DarkGray } end if m.id_lm then - m.id_lm = 72 + m.id_lm = 73 m.attr_lm = {} end end diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 639d43b560..5ef1ef54bb 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -2956,6 +2956,28 @@ describe('extmark decorations', function() {1:~ }|*3 | ]]) + -- No scrolling for concealed topline #33033 + api.nvim_buf_clear_namespace(0, ns, 0, -1) + api.nvim_buf_set_extmark(0, ns, 1, 0, { virt_lines_above = true, virt_lines = { { { "virt_above 2" } } } }) + api.nvim_buf_set_extmark(0, ns, 0, 0, { conceal_lines = "" }) + feed('ggjj') + screen:expect([[ + {2: }virt_above 2 | + {2: 2 } local text, hl_id_cell, count = unpack(ite| + {2: }m) | + {2: 3 }^ if hl_id_cell ~= nil then | + {2: 4 } hl_id = hl_id_cell | + {2: 5 }conceal text | + {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:~ }| + | + ]]) end) end) diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua index e0d88771d3..223622eb6b 100644 --- a/test/functional/ui/diff_spec.lua +++ b/test/functional/ui/diff_spec.lua @@ -1555,7 +1555,6 @@ it('diff mode overlapped diff blocks will be merged', function() local screen = Screen.new(35, 20) command('set winwidth=10 diffopt=filler,internal') - command('args Xdifile1 Xdifile2 | vert all | windo diffthis') WriteDiffFiles('a\nb', 'x\nx') @@ -2252,3 +2251,536 @@ it('diff mode does not scroll with line("w0")', function() 9 | ]]) end) + +-- oldtest: Test_diff_inline() +it('diff mode inline highlighting', function() + write_file('Xdifile1', '') + write_file('Xdifile2', '') + finally(function() + os.remove('Xdifile1') + os.remove('Xdifile2') + end) + + local screen = Screen.new(37, 20) + screen:add_extra_attr_ids({ + [100] = { background = Screen.colors.Blue1 }, + [101] = { bold = true, background = Screen.colors.Red, foreground = Screen.colors.Blue1 }, + [102] = { background = Screen.colors.LightMagenta, foreground = Screen.colors.Blue1 }, + [103] = { bold = true, background = Screen.colors.Blue1, foreground = Screen.colors.Blue1 }, + [104] = { bold = true, background = Screen.colors.LightBlue, foreground = Screen.colors.Blue1 }, + }) + command('set winwidth=10') + command('args Xdifile1 Xdifile2 | vert all | windo diffthis | 1wincmd w') + + WriteDiffFiles('abcdef ghi jk n\nx\ny', 'aBcef gHi lm n\ny\nz') + command('set diffopt=internal,filler') + local s1 = [[ + {7: }{4:^a}{27:bcdef ghi jk}{4: n }│{7: }{4:a}{27:Bcef gHi lm}{4: n }| + {7: }{22:x }│{7: }{23:----------------}| + {7: }y │{7: }y | + {7: }{23:----------------}│{7: }{22:z }| + {1:~ }│{1:~ }|*14 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]] + screen:expect(s1) + command('set diffopt=internal,filler diffopt+=inline:none') + local s2 = [[ + {7: }{4:^abcdef ghi jk n }│{7: }{4:aBcef gHi lm n }| + {7: }{22:x }│{7: }{23:----------------}| + {7: }y │{7: }y | + {7: }{23:----------------}│{7: }{22:z }| + {1:~ }│{1:~ }|*14 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]] + screen:expect(s2) + + -- inline:simple is the same as default + command('set diffopt=internal,filler diffopt+=inline:simple') + screen:expect(s1) + + command('set diffopt=internal,filler diffopt+=inline:char') + local s3 = [[ + {7: }{4:^a}{27:b}{4:c}{27:d}{4:ef g}{27:h}{4:i }{27:jk}{4: n }│{7: }{4:a}{27:B}{4:cef g}{27:H}{4:i }{27:lm}{4: n }| + {7: }{22:x }│{7: }{23:----------------}| + {7: }y │{7: }y | + {7: }{23:----------------}│{7: }{22:z }| + {1:~ }│{1:~ }|*14 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]] + screen:expect(s3) + + command('set diffopt=internal,filler diffopt+=inline:word') + screen:expect([[ + {7: }{27:^abcdef}{4: }{27:ghi}{4: }{27:jk}{4: n }│{7: }{27:aBcef}{4: }{27:gHi}{4: }{27:lm}{4: n }| + {7: }{22:x }│{7: }{23:----------------}| + {7: }y │{7: }y | + {7: }{23:----------------}│{7: }{22:z }| + {1:~ }│{1:~ }|*14 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + + -- multiple inline values will the last one + command('set diffopt=internal,filler diffopt+=inline:none,inline:char,inline:simple') + screen:expect(s1) + command('set diffopt=internal,filler diffopt+=inline:simple,inline:word,inline:none') + screen:expect(s2) + command('set diffopt=internal,filler diffopt+=inline:simple,inline:word,inline:char') + screen:expect(s3) + + -- DiffTextAdd highlight + command('hi DiffTextAdd guibg=blue') + command('set diffopt=internal,filler diffopt+=inline:char') + screen:expect([[ + {7: }{4:^a}{27:b}{4:c}{100:d}{4:ef g}{27:h}{4:i }{27:jk}{4: n }│{7: }{4:a}{27:B}{4:cef g}{27:H}{4:i }{27:lm}{4: n }| + {7: }{22:x }│{7: }{23:----------------}| + {7: }y │{7: }y | + {7: }{23:----------------}│{7: }{22:z }| + {1:~ }│{1:~ }|*14 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + + -- Live update in insert mode + feed('isometext') + screen:expect([[ + {7: }{27:sometext^abcd}{4:ef g}│{7: }{27:aBc}{4:ef g}{27:H}{4:i }{27:lm}{4: n }| + {7: }{22:x }│{7: }{23:----------------}| + {7: }y │{7: }y | + {7: }{23:----------------}│{7: }{22:z }| + {1:~ }│{1:~ }|*14 + {3:Xdifile1 [+] }{2:Xdifile2 }| + {5:-- INSERT --} | + ]]) + feed('<Esc>') + command('silent! undo') + + -- icase simple scenarios + command('set diffopt=internal,filler diffopt+=inline:simple,icase') + screen:expect([[ + {7: }{4:^abc}{27:def ghi jk}{4: n }│{7: }{4:aBc}{27:ef gHi lm}{4: n }| + {7: }{22:x }│{7: }{23:----------------}| + {7: }y │{7: }y | + {7: }{23:----------------}│{7: }{22:z }| + {1:~ }│{1:~ }|*14 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + command('set diffopt=internal,filler diffopt+=inline:char,icase') + screen:expect([[ + {7: }{4:^abc}{100:d}{4:ef ghi }{27:jk}{4: n }│{7: }{4:aBcef gHi }{27:lm}{4: n }| + {7: }{22:x }│{7: }{23:----------------}| + {7: }y │{7: }y | + {7: }{23:----------------}│{7: }{22:z }| + {1:~ }│{1:~ }|*14 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + command('set diffopt=internal,filler diffopt+=inline:word,icase') + screen:expect([[ + {7: }{27:^abcdef}{4: ghi }{27:jk}{4: n }│{7: }{27:aBcef}{4: gHi }{27:lm}{4: n }| + {7: }{22:x }│{7: }{23:----------------}| + {7: }y │{7: }y | + {7: }{23:----------------}│{7: }{22:z }| + {1:~ }│{1:~ }|*14 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + + screen:try_resize(45, 20) + command('wincmd =') + -- diff algorithms should affect highlight + WriteDiffFiles('apples and oranges', 'oranges and apples') + command('set diffopt=internal,filler diffopt+=inline:char') + screen:expect([[ + {7: }{27:^appl}{4:es and }{27:orang}{4:es }│{7: }{27:orang}{4:es and }{27:appl}{4:es }| + {1:~ }│{1:~ }|*17 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + command('set diffopt=internal,filler diffopt+=inline:char,algorithm:patience') + screen:expect([[ + {7: }{100:^apples and }{4:oranges }│{7: }{4:oranges}{100: and apples}{4: }| + {1:~ }│{1:~ }|*17 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + + screen:try_resize(65, 20) + command('wincmd =') + -- icase: composing chars and Unicode fold case edge cases + WriteDiffFiles( + '1 - sigma in 6σ and Ὀδυσσεύς\n1 - angstrom in åå\n1 - composing: ii⃗I⃗', + '2 - Sigma in 6Σ and ὈΔΥΣΣΕΎΣ\n2 - Angstrom in ÅÅ\n2 - Composing: i⃗I⃗I⃗' + ) + command('set diffopt=internal,filler diffopt+=inline:char') + screen:expect([[ + {7: }{27:^1}{4: - }{27:s}{4:igma in 6}{27:σ}{4: and Ὀ}{27:δυσσεύς}{4: }│{7: }{27:2}{4: - }{27:S}{4:igma in 6}{27:Σ}{4: and Ὀ}{27:ΔΥΣΣΕΎΣ}{4: }| + {7: }{27:1}{4: - }{27:a}{4:ngstrom in }{27:åå}{4: }│{7: }{27:2}{4: - }{27:A}{4:ngstrom in }{27:ÅÅ}{4: }| + {7: }{27:1}{4: - }{27:c}{4:omposing: }{100:i}{4:i⃗I⃗ }│{7: }{27:2}{4: - }{27:C}{4:omposing: i⃗I⃗}{100:I⃗}{4: }| + {1:~ }│{1:~ }|*15 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + command('set diffopt=internal,filler diffopt+=inline:char,icase') + screen:expect([[ + {7: }{27:^1}{4: - sigma in 6σ and Ὀδυσσεύς }│{7: }{27:2}{4: - Sigma in 6Σ and ὈΔΥΣΣΕΎΣ }| + {7: }{27:1}{4: - angstrom in åå }│{7: }{27:2}{4: - Angstrom in ÅÅ }| + {7: }{27:1}{4: - composing: }{27:i}{4:i⃗I⃗ }│{7: }{27:2}{4: - Composing: }{27:i⃗}{4:I⃗I⃗ }| + {1:~ }│{1:~ }|*15 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + + screen:try_resize(35, 20) + command('wincmd =') + -- wide chars + WriteDiffFiles('abc😅xde一\nf🚀g', 'abcy😢de\n二f🚀g') + command('set diffopt=internal,filler diffopt+=inline:char,icase') + screen:expect([[ + {7: }{4:^abc}{27:😅x}{4:de}{100:一}{4: }│{7: }{4:abc}{27:y😢}{4:de }| + {7: }{4:f🚀g }│{7: }{100:二}{4:f🚀g }| + {1:~ }│{1:~ }|*16 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + + -- NUL char + WriteDiffFiles('1\00034\0005\0006', '1234\0005\n6') + command('set diffopt=internal,filler diffopt+=inline:char') + screen:expect([[ + {7: }{4:^1}{101:^@}{4:34}{102:^@}{4:5}{101:^@}{4:6 }│{7: }{4:1}{27:2}{4:34}{102:^@}{4:5 }| + {7: }{23:---------------}│{7: }{4:6 }| + {1:~ }│{1:~ }|*16 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + + -- word diff: always use first buffer's iskeyword and ignore others' for consistency + WriteDiffFiles('foo+bar test', 'foo+baz test') + command('set diffopt=internal,filler diffopt+=inline:word') + local sw1 = [[ + {7: }{4:^foo+}{27:bar}{4: test }│{7: }{4:foo+}{27:baz}{4: test }| + {1:~ }│{1:~ }|*17 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]] + screen:expect(sw1) + + command('set iskeyword+=+ | diffupdate') + screen:expect([[ + {7: }{27:^foo+bar}{4: test }│{7: }{27:foo+baz}{4: test }| + {1:~ }│{1:~ }|*17 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + + command('set iskeyword& | wincmd w') + command('set iskeyword+=+ | wincmd w | diffupdate') + -- Use the previous screen as 2nd buffer's iskeyword does not matter + screen:expect(sw1) + + command('windo set iskeyword& | 1wincmd w') + + screen:try_resize(69, 20) + command('wincmd =') + -- char diff: should slide highlight to whitespace boundary if possible for + -- better readability (by using forced indent-heuristics). A wrong result + -- would be if the highlight is "Bar, prefix". It should be "prefixBar, " + -- instead. + WriteDiffFiles('prefixFoo, prefixEnd', 'prefixFoo, prefixBar, prefixEnd') + command('set diffopt=internal,filler diffopt+=inline:char') + screen:expect([[ + {7: }{4:^prefixFoo, prefixEnd }│{7: }{4:prefixFoo, }{100:prefixBar, }{4:prefixEnd }| + {1:~ }│{1:~ }|*17 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + + screen:try_resize(39, 20) + command('wincmd =') + -- char diff: small gaps between inline diff blocks will be merged during refine step + -- - first segment: test that we iteratively merge small gaps after we merged + -- adjacent blocks, but only with limited number (set to 4) of iterations. + -- - second and third segments: show that we need a large enough adjacent block to + -- trigger a merge. + -- - fourth segment: small gaps are not merged when adjacent large block is + -- on a different line. + WriteDiffFiles( + 'abcdefghijklmno\nanchor1\n' + .. 'abcdefghijklmno\nanchor2\n' + .. 'abcdefghijklmno\nanchor3\n' + .. 'test\nmultiline', + 'a?c?e?g?i?k???o\nanchor1\n' + .. 'a??de?????klmno\nanchor2\n' + .. 'a??de??????lmno\nanchor3\n' + .. 't?s?\n??????i?e' + ) + command('set diffopt=internal,filler diffopt+=inline:char') + screen:expect([[ + {7: }{4:^a}{27:b}{4:c}{27:defghijklmn}{4:o }│{7: }{4:a}{27:?}{4:c}{27:?e?g?i?k???}{4:o }| + {7: }anchor1 │{7: }anchor1 | + {7: }{4:a}{27:bc}{4:de}{27:fghij}{4:klmno }│{7: }{4:a}{27:??}{4:de}{27:?????}{4:klmno }| + {7: }anchor2 │{7: }anchor2 | + {7: }{4:a}{27:bcdefghijk}{4:lmno }│{7: }{4:a}{27:??de??????}{4:lmno }| + {7: }anchor3 │{7: }anchor3 | + {7: }{4:t}{27:e}{4:s}{27:t}{4: }│{7: }{4:t}{27:?}{4:s}{27:?}{4: }| + {7: }{27:multilin}{4:e }│{7: }{27:??????i?}{4:e }| + {1:~ }│{1:~ }|*10 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + + screen:try_resize(49, 20) + command('wincmd =') + -- Test multi-line blocks and whitespace + WriteDiffFiles( + 'this is \nsometest text foo\nbaz abc def \none\nword another word\nadditional line', + 'this is some test\ntexts\nfoo bar abX Yef \noneword another word' + ) + command('set diffopt=internal,filler diffopt+=inline:char,iwhite') + screen:expect([[ + {7: }{4:^this is }│{7: }{4:this is some}{100: }{4:test }| + {7: }{4:sometest text foo }│{7: }{4:text}{100:s}{4: }| + {7: }{4:ba}{27:z}{4: ab}{27:c}{4: }{27:d}{4:ef }│{7: }{4:foo ba}{27:r}{4: ab}{27:X}{4: }{27:Y}{4:ef }| + {7: }{4:one }│{7: }{4:oneword another word }| + {7: }{4:word another word }│{7: }{23:----------------------}| + {7: }{22:additional line }│{7: }{23:----------------------}| + {1:~ }│{1:~ }|*12 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + command('set diffopt=internal,filler diffopt+=inline:word,iwhite') + screen:expect([[ + {7: }{4:^this is }│{7: }{4:this is }{27:some}{4: }{27:test}{4: }| + {7: }{27:sometest}{4: }{27:text}{4: }{27:foo}{4: }│{7: }{27:texts}{4: }| + {7: }{27:baz}{4: }{27:abc}{4: }{27:def}{4: }│{7: }{27:foo}{4: }{27:bar}{4: }{27:abX}{4: }{27:Yef}{4: }| + {7: }{27:one}{4: }│{7: }{27:oneword}{4: another word }| + {7: }{27:word}{4: another word }│{7: }{23:----------------------}| + {7: }{22:additional line }│{7: }{23:----------------------}| + {1:~ }│{1:~ }|*12 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + command('set diffopt=internal,filler diffopt+=inline:char,iwhiteeol') + screen:expect([[ + {7: }{4:^this }{100: }{4:is }│{7: }{4:this is some}{100: }{4:test }| + {7: }{4:sometest text foo }│{7: }{4:text}{100:s}{4: }| + {7: }{4:ba}{27:z}{4: ab}{27:c}{4: }{27:d}{4:ef }│{7: }{4:foo ba}{27:r}{4: ab}{27:X}{4: }{27:Y}{4:ef }| + {7: }{4:one }│{7: }{4:oneword another word }| + {7: }{4:word another word }│{7: }{23:----------------------}| + {7: }{22:additional line }│{7: }{23:----------------------}| + {1:~ }│{1:~ }|*12 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + command('set diffopt=internal,filler diffopt+=inline:word,iwhiteeol') + screen:expect([[ + {7: }{4:^this }{100: }{4:is }│{7: }{4:this is }{27:some}{4: }{27:test}{4: }| + {7: }{27:sometest}{4: }{27:text}{4: foo }│{7: }{27:texts}{4: }| + {7: }{27:baz}{4: }{27:abc}{4: }{27:def}{4: }│{7: }{4:foo }{27:bar}{4: }{27:abX}{4: }{27:Yef}{4: }| + {7: }{27:one}{4: }│{7: }{27:oneword}{4: another word }| + {7: }{27:word}{4: another word }│{7: }{23:----------------------}| + {7: }{22:additional line }│{7: }{23:----------------------}| + {1:~ }│{1:~ }|*12 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + command('set diffopt=internal,filler diffopt+=inline:char,iwhiteall') + screen:expect([[ + {7: }{4:^this is }│{7: }{4:this is some test }| + {7: }{4:sometest text foo }│{7: }{4:text}{100:s}{4: }| + {7: }{4:ba}{27:z}{4: ab}{27:c d}{4:ef }│{7: }{4:foo ba}{27:r}{4: ab}{27:X Y}{4:ef }| + {7: }{4:one }│{7: }{4:oneword another word }| + {7: }{4:word another word }│{7: }{23:----------------------}| + {7: }{22:additional line }│{7: }{23:----------------------}| + {1:~ }│{1:~ }|*12 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + command('set diffopt=internal,filler diffopt+=inline:word,iwhiteall') + screen:expect([[ + {7: }{4:^this is }│{7: }{4:this is }{27:some test}{4: }| + {7: }{27:sometest text}{4: foo }│{7: }{27:texts}{4: }| + {7: }{27:baz abc def }{4: }│{7: }{4:foo }{27:bar abX Yef }{4: }| + {7: }{27:one}{4: }│{7: }{27:oneword}{4: another word }| + {7: }{27:word}{4: another word }│{7: }{23:----------------------}| + {7: }{22:additional line }│{7: }{23:----------------------}| + {1:~ }│{1:~ }|*12 + {3:Xdifile1 }{2:Xdifile2 }| + | + ]]) + + -- newline should be highlighted too when 'list' is set + command('windo set list listchars=eol:$') + command('set diffopt=internal,filler diffopt+=inline:char') + screen:expect([[ + {7: }{4:this }{100: }{4:is }{100: }{103:$}{4: }│{7: }{4:^this is some}{100: }{4:test}{101:$}{4: }| + {7: }{4:sometest}{27: }{4:text}{27: }{4:foo}{101:$}{4: }│{7: }{4:text}{27:s}{101:$}{4: }| + {7: }{4:ba}{27:z}{4: ab}{27:c}{4: }{27:d}{4:ef }{11:$}{4: }│{7: }{4:foo}{27: }{4:ba}{27:r}{4: ab}{27:X}{4: }{27:Y}{4:ef }{100: }{11:$}{4: }| + {7: }{4:one}{103:$}{4: }│{7: }{4:oneword another word}{11:$}{4: }| + {7: }{4:word another word}{11:$}{4: }│{7: }{23:----------------------}| + {7: }{22:additional line}{104:$}{22: }│{7: }{23:----------------------}| + {1:~ }│{1:~ }|*12 + {2:Xdifile1 }{3:Xdifile2 }| + | + ]]) + command('set diffopt=internal,filler diffopt+=inline:char,iwhite') + screen:expect([[ + {7: }{4:this is }{11:$}{4: }│{7: }{4:^this is some}{100: }{4:test}{11:$}{4: }| + {7: }{4:sometest text foo}{11:$}{4: }│{7: }{4:text}{100:s}{11:$}{4: }| + {7: }{4:ba}{27:z}{4: ab}{27:c}{4: }{27:d}{4:ef }{11:$}{4: }│{7: }{4:foo ba}{27:r}{4: ab}{27:X}{4: }{27:Y}{4:ef }{11:$}{4: }| + {7: }{4:one}{103:$}{4: }│{7: }{4:oneword another word}{11:$}{4: }| + {7: }{4:word another word}{11:$}{4: }│{7: }{23:----------------------}| + {7: }{22:additional line}{104:$}{22: }│{7: }{23:----------------------}| + {1:~ }│{1:~ }|*12 + {2:Xdifile1 }{3:Xdifile2 }| + | + ]]) + command('set diffopt=internal,filler diffopt+=inline:char,iwhiteeol') + screen:expect([[ + {7: }{4:this }{100: }{4:is }{11:$}{4: }│{7: }{4:^this is some}{100: }{4:test}{11:$}{4: }| + {7: }{4:sometest text foo}{11:$}{4: }│{7: }{4:text}{100:s}{11:$}{4: }| + {7: }{4:ba}{27:z}{4: ab}{27:c}{4: }{27:d}{4:ef }{11:$}{4: }│{7: }{4:foo ba}{27:r}{4: ab}{27:X}{4: }{27:Y}{4:ef }{11:$}{4: }| + {7: }{4:one}{103:$}{4: }│{7: }{4:oneword another word}{11:$}{4: }| + {7: }{4:word another word}{11:$}{4: }│{7: }{23:----------------------}| + {7: }{22:additional line}{104:$}{22: }│{7: }{23:----------------------}| + {1:~ }│{1:~ }|*12 + {2:Xdifile1 }{3:Xdifile2 }| + | + ]]) + command('set diffopt=internal,filler diffopt+=inline:char,iwhiteall') + screen:expect([[ + {7: }{4:this is }{11:$}{4: }│{7: }{4:^this is some test}{11:$}{4: }| + {7: }{4:sometest text foo}{11:$}{4: }│{7: }{4:text}{100:s}{11:$}{4: }| + {7: }{4:ba}{27:z}{4: ab}{27:c d}{4:ef }{11:$}{4: }│{7: }{4:foo ba}{27:r}{4: ab}{27:X Y}{4:ef }{11:$}{4: }| + {7: }{4:one}{11:$}{4: }│{7: }{4:oneword another word}{11:$}{4: }| + {7: }{4:word another word}{11:$}{4: }│{7: }{23:----------------------}| + {7: }{22:additional line}{104:$}{22: }│{7: }{23:----------------------}| + {1:~ }│{1:~ }|*12 + {2:Xdifile1 }{3:Xdifile2 }| + | + ]]) + command('windo set nolist') +end) + +-- oldtest: Test_diff_inline_multibuffer() +it('diff mode inline highlighting with 3 buffers', function() + write_file('Xdifile1', '') + write_file('Xdifile2', '') + write_file('Xdifile3', '') + finally(function() + os.remove('Xdifile1') + os.remove('Xdifile2') + os.remove('Xdifile3') + end) + + local screen = Screen.new(75, 20) + screen:add_extra_attr_ids({ + [100] = { background = Screen.colors.Blue1 }, + }) + command('args Xdifile1 Xdifile2 Xdifile3 | vert all | windo diffthis | 1wincmd w') + command('wincmd =') + command('hi DiffTextAdd guibg=Blue') + + WriteDiffFiles3( + 'That is buffer1.\nanchor\nSome random text\nanchor', + 'This is buffer2.\nanchor\nSome text\nanchor\nbuffer2/3', + 'This is buffer3. Last.\nanchor\nSome more\ntext here.\nanchor\nonly in buffer2/3\nnot in buffer1' + ) + command('set diffopt=internal,filler diffopt+=inline:char') + local s1 = [[ + {7: }{4:^Th}{27:at}{4: is buffer}{27:1}{4:. }│{7: }{4:Th}{27:is}{4: is buffer}{27:2}{4:. }│{7: }{4:Th}{27:is}{4: is buffer}{27:3. Last}{4:.}| + {7: }anchor │{7: }anchor │{7: }anchor | + {7: }{4:Some }{27:random }{4:text }│{7: }{4:Some text }│{7: }{4:Some }{27:more}{4: }| + {7: }{23:-----------------------}│{7: }{23:----------------------}│{7: }{4:text}{100: here.}{4: }| + {7: }anchor │{7: }anchor │{7: }anchor | + {7: }{23:-----------------------}│{7: }{4:buffer2/3 }│{7: }{100:only in }{4:buffer2/3 }| + {7: }{23:-----------------------}│{7: }{23:----------------------}│{7: }{22:not in buffer1 }| + {1:~ }│{1:~ }│{1:~ }|*11 + {3:Xdifile1 }{2:Xdifile2 Xdifile3 }| + | + ]] + screen:expect(s1) + + -- Close one of the buffers and make sure it updates correctly + command('diffoff') + screen:expect([[ + ^That is buffer1. │{7: }{4:This is buffer}{27:2}{4:. }│{7: }{4:This is buffer}{27:3. Last}{4:.}| + anchor │{7: }anchor │{7: }anchor | + Some random text │{7: }{4:Some text }│{7: }{4:Some }{100:more}{4: }| + anchor │{7: }{23:----------------------}│{7: }{4:text}{100: here.}{4: }| + {1:~ }│{7: }anchor │{7: }anchor | + {1:~ }│{7: }{4:buffer2/3 }│{7: }{100:only in }{4:buffer2/3 }| + {1:~ }│{7: }{23:----------------------}│{7: }{22:not in buffer1 }| + {1:~ }│{1:~ }│{1:~ }|*11 + {3:Xdifile1 }{2:Xdifile2 Xdifile3 }| + | + ]]) + + -- Update text in the non-diff buffer and nothing should be changed + feed('isometext') + screen:expect([[ + sometext^That is buffer1. │{7: }{4:This is buffer}{27:2}{4:. }│{7: }{4:This is buffer}{27:3. Last}{4:.}| + anchor │{7: }anchor │{7: }anchor | + Some random text │{7: }{4:Some text }│{7: }{4:Some }{100:more}{4: }| + anchor │{7: }{23:----------------------}│{7: }{4:text}{100: here.}{4: }| + {1:~ }│{7: }anchor │{7: }anchor | + {1:~ }│{7: }{4:buffer2/3 }│{7: }{100:only in }{4:buffer2/3 }| + {1:~ }│{7: }{23:----------------------}│{7: }{22:not in buffer1 }| + {1:~ }│{1:~ }│{1:~ }|*11 + {3:Xdifile1 [+] }{2:Xdifile2 Xdifile3 }| + {5:-- INSERT --} | + ]]) + feed('<Esc>') + command('silent! undo') + + command('diffthis') + screen:expect(s1) + + -- Test that removing first buffer from diff will in turn use the next + -- earliest buffer's iskeyword during word diff. + WriteDiffFiles3('This+is=a-setence', 'This+is=another-setence', 'That+is=a-setence') + command('set iskeyword+=+ | 2wincmd w | set iskeyword+=- | 1wincmd w') + command('set diffopt=internal,filler diffopt+=inline:word') + local s4 = [[ + {7: }{27:^This+is}{4:=}{27:a}{4:-setence }│{7: }{27:This+is}{4:=}{27:another}{4:-setenc}│{7: }{27:That+is}{4:=}{27:a}{4:-setence }| + {1:~ }│{1:~ }│{1:~ }|*17 + {3:Xdifile1 }{2:Xdifile2 Xdifile3 }| + | + ]] + screen:expect(s4) + command('diffoff') + screen:expect([[ + ^This+is=a-setence │{7: }{27:This}{4:+is=}{27:another-setenc}│{7: }{27:That}{4:+is=}{27:a-setence}{4: }| + {1:~ }│{1:~ }│{1:~ }|*17 + {3:Xdifile1 }{2:Xdifile2 Xdifile3 }| + | + ]]) + command('diffthis') + screen:expect(s4) + + -- Test multi-buffer char diff refinement, and that removing a buffer from + -- diff will update the others properly. + WriteDiffFiles3('abcdefghijkYmYYY', 'aXXdXXghijklmnop', 'abcdefghijkYmYop') + command('set diffopt=internal,filler diffopt+=inline:char') + local s6 = [[ + {7: }{4:^a}{27:bcdef}{4:ghijk}{27:YmYYY}{4: }│{7: }{4:a}{27:XXdXX}{4:ghijk}{27:lmnop}{4: }│{7: }{4:a}{27:bcdef}{4:ghijk}{27:YmYop}{4: }| + {1:~ }│{1:~ }│{1:~ }|*17 + {3:Xdifile1 }{2:Xdifile2 Xdifile3 }| + | + ]] + screen:expect(s6) + command('diffoff') + screen:expect([[ + ^abcdefghijkYmYYY │{7: }{4:a}{27:XXdXX}{4:ghijk}{27:l}{4:m}{27:n}{4:op }│{7: }{4:a}{27:bcdef}{4:ghijk}{27:Y}{4:m}{27:Y}{4:op }| + {1:~ }│{1:~ }│{1:~ }|*17 + {3:Xdifile1 }{2:Xdifile2 Xdifile3 }| + | + ]]) + command('diffthis') + screen:expect(s6) +end) diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 9b01150411..94faafa9be 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -229,11 +229,11 @@ describe('ui/ext_messages', function() { content = { { '\n@character ' }, - { 'xxx', 26, 155 }, + { 'xxx', 26, 156 }, { ' ' }, { 'links to', 18, 5 }, { ' Character\n@character.special ' }, - { 'xxx', 16, 156 }, + { 'xxx', 16, 157 }, { ' ' }, { 'links to', 18, 5 }, { ' SpecialChar' }, @@ -300,7 +300,7 @@ describe('ui/ext_messages', function() cmdline = { { abort = false } }, messages = { { - content = { { 'Error', 9, 6 }, { 'Message', 16, 99 } }, + content = { { 'Error', 9, 6 }, { 'Message', 16, 100 } }, history = true, kind = 'echoerr', }, @@ -940,7 +940,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - ruler = { { '0,0-1 All', 9, 61 } }, + ruler = { { '0,0-1 All', 9, 62 } }, }) command('hi clear MsgArea') feed('i') diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index 3ee4d429c7..90b3094082 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -1719,6 +1719,20 @@ describe('ui/mouse/input', function() ]]) end) + it("mouse click on window separator in statusline doesn't crash", function() + api.nvim_set_option_value('winwidth', 1, {}) + api.nvim_set_option_value('statusline', '%f', {}) + + command('vsplit') + command('redraw') + + local lines = api.nvim_get_option_value('lines', {}) + local columns = api.nvim_get_option_value('columns', {}) + + api.nvim_input_mouse('left', 'press', '', 0, lines - 1, math.floor(columns / 2)) + command('redraw') + end) + it('getmousepos() works correctly', function() local winwidth = api.nvim_get_option_value('winwidth', {}) -- Set winwidth=1 so that window sizes don't change. diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 9403f39ba1..c73cec22a0 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -5478,7 +5478,489 @@ describe('builtin popupmenu', function() end end) - it('does not crash when displayed in the last column with rightleft #12032', function() + -- oldtest: Test_pum_maxwidth() + it('"pummaxwidth"', function() + screen:try_resize(60, 8) + api.nvim_buf_set_lines(0, 0, -1, true, { + '123456789_123456789_123456789_a', + '123456789_123456789_123456789_b', + ' 123', + }) + feed('G"zyy') + feed('A<C-X><C-N>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }|*4 + ## grid 3 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ## grid 4 + {s: 123456789_123456789_123456789_a }| + {n: 123456789_123456789_123456789_b }| + ]], + float_pos = { [4] = { -1, 'NW', 2, 3, 11, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }{s: 123456789_123456789_123456789_a }{1: }| + {1:~ }{n: 123456789_123456789_123456789_b }{1: }| + {1:~ }|*2 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ]]) + end + feed('<Esc>3Gdd"zp') + + command('set pummaxwidth=10') + feed('GA<C-X><C-N>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }|*4 + ## grid 3 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ## grid 4 + {s: 1234567...}| + {n: 1234567...}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 3, 11, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }{s: 1234567...}{1: }| + {1:~ }{n: 1234567...}{1: }| + {1:~ }|*2 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ]]) + end + feed('<Esc>3Gdd"zp') + + command('set pummaxwidth=20') + feed('GA<C-X><C-N>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }|*4 + ## grid 3 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ## grid 4 + {s: 123456789_1234567...}| + {n: 123456789_1234567...}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 3, 11, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }{s: 123456789_1234567...}{1: }| + {1:~ }{n: 123456789_1234567...}{1: }| + {1:~ }|*2 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ]]) + end + feed('<Esc>3Gdd"zp') + + command('set pumwidth=20 pummaxwidth=8') + feed('GA<C-X><C-N>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }|*4 + ## grid 3 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ## grid 4 + {s: 12345...}| + {n: 12345...}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 3, 11, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_123456789_a^ | + {1:~ }{s: 12345...}{1: }| + {1:~ }{n: 12345...}{1: }| + {1:~ }|*2 + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 2} | + ]]) + end + feed('<Esc>3Gdd"zp') + + screen:try_resize(32, 10) + feed('GA<C-X><C-N>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*9 + [3:--------------------------------]| + ## grid 2 + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_| + 123456789_a^ | + {1:~ }|*5 + ## grid 3 + {2:-- }{5:match 1 of 2} | + ## grid 4 + {s: 12345...}| + {n: 12345...}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 4, 11, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_a | + 123456789_123456789_123456789_b | + 123456789_123456789_| + 123456789_a^ | + {1:~ }{s: 12345...}{1: }| + {1:~ }{n: 12345...}{1: }| + {1:~ }|*3 + {2:-- }{5:match 1 of 2} | + ]]) + end + feed('<Esc>3Gdd"zp') + end) + + -- oldtest: Test_pum_maxwidth_multibyte() + it("'pummaxwidth' with multibyte", function() + screen:try_resize(60, 8) + exec([[ + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [ + \ #{word: "123456789_123456789_123456789_"}, + \ #{word: "一二三四五六七八九十"}, + \ #{word: "abcdefghij"}, + \ #{word: "上下左右"}, + \ ] + endfunc + set omnifunc=Omni_test + ]]) + + feed('S<C-X><C-O>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + 123456789_123456789_123456789_^ | + {1:~ }|*6 + ## grid 3 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 4} | + ## grid 4 + {s:123456789_123456789_123456789_ }| + {n:一二三四五六七八九十 }| + {n:abcdefghij }| + {n:上下左右 }| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_^ | + {s:123456789_123456789_123456789_ }{1: }| + {n:一二三四五六七八九十 }{1: }| + {n:abcdefghij }{1: }| + {n:上下左右 }{1: }| + {1:~ }|*2 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 4} | + ]]) + end + feed('<Esc>') + + command('set pummaxwidth=10') + feed('S<C-X><C-O>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + 123456789_123456789_123456789_^ | + {1:~ }|*6 + ## grid 3 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 4} | + ## grid 4 + {s:1234567...}| + {n:一二三 ...}| + {n:abcdefghij}| + {n:上下左右 }| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_^ | + {s:1234567...}{1: }| + {n:一二三 ...}{1: }| + {n:abcdefghij}{1: }| + {n:上下左右 }{1: }| + {1:~ }|*2 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 4} | + ]]) + end + feed('<Esc>') + + command('set rightleft') + feed('S<C-X><C-O>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + ^ _987654321_987654321_987654321| + {1: ~}|*6 + ## grid 3 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 4} | + ## grid 4 + {s:...7654321}| + {n:... 三二一}| + {n:jihgfedcba}| + {n: 右左下上}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 50, false, 100 } }, + }) + else + screen:expect([[ + ^ _987654321_987654321_987654321| + {1: }{s:...7654321}| + {1: }{n:... 三二一}| + {1: }{n:jihgfedcba}| + {1: }{n: 右左下上}| + {1: ~}|*2 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 4} | + ]]) + end + feed('<Esc>') + command('set norightleft') + + command('set pummaxwidth=2') + feed('S<C-X><C-O>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:------------------------------------------------------------]|*7 + [3:------------------------------------------------------------]| + ## grid 2 + 123456789_123456789_123456789_^ | + {1:~ }|*6 + ## grid 3 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 4} | + ## grid 4 + {s:12}| + {n:一}| + {n:ab}| + {n:上}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100 } }, + }) + else + screen:expect([[ + 123456789_123456789_123456789_^ | + {s:12}{1: }| + {n:一}{1: }| + {n:ab}{1: }| + {n:上}{1: }| + {1:~ }|*2 + {2:-- Omni completion (^O^N^P) }{5:match 1 of 4} | + ]]) + end + feed('<Esc>') + end) + + it([['pummaxwidth' works with "kind" and "menu"]], function() + exec([[ + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [ + \ #{word: "foo", menu: "fooMenu", kind: "fooKind"}, + \ #{word: "bar", menu: "barMenu", kind: "barKind"}, + \ #{word: "baz", menu: "bazMenu", kind: "bazKind"}, + \ ] + endfunc + set omnifunc=Omni_test + ]]) + + command('set pummaxwidth=14') + feed('S<C-X><C-O>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*19 + [3:--------------------------------]| + ## grid 2 + foo^ | + {1:~ }|*18 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {s:foo fooKind...}| + {n:bar barKind...}| + {n:baz bazKind...}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100 } }, + }) + else + screen:expect([[ + foo^ | + {s:foo fooKind...}{1: }| + {n:bar barKind...}{1: }| + {n:baz bazKind...}{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + end + feed('<Esc>') + + command('set rightleft') + feed('S<C-X><C-O>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*19 + [3:--------------------------------]| + ## grid 2 + ^ oof| + {1: ~}|*18 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {s:...dniKoof oof}| + {n:...dniKrab rab}| + {n:...dniKzab zab}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 18, false, 100 } }, + }) + else + screen:expect([[ + ^ oof| + {1: }{s:...dniKoof oof}| + {1: }{n:...dniKrab rab}| + {1: }{n:...dniKzab zab}| + {1: ~}|*15 + {2:-- }{5:match 1 of 3} | + ]]) + end + feed('<Esc>') + command('set norightleft') + + command('set pummaxwidth=13') + feed('S<C-X><C-O>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*19 + [3:--------------------------------]| + ## grid 2 + foo^ | + {1:~ }|*18 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {s:foo fooKin...}| + {n:bar barKin...}| + {n:baz bazKin...}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100 } }, + }) + else + screen:expect([[ + foo^ | + {s:foo fooKin...}{1: }| + {n:bar barKin...}{1: }| + {n:baz bazKin...}{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + end + feed('<Esc>') + + command('set rightleft') + feed('S<C-X><C-O>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*19 + [3:--------------------------------]| + ## grid 2 + ^ oof| + {1: ~}|*18 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {s:...niKoof oof}| + {n:...niKrab rab}| + {n:...niKzab zab}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 19, false, 100 } }, + }) + else + screen:expect([[ + ^ oof| + {1: }{s:...niKoof oof}| + {1: }{n:...niKrab rab}| + {1: }{n:...niKzab zab}| + {1: ~}|*15 + {2:-- }{5:match 1 of 3} | + ]]) + end + feed('<Esc>') + command('set norightleft') + end) + + it('does not crash when displayed in last column with rightleft #12032', function() local col = 30 local items = { 'word', 'choice', 'text', 'thing' } local max_len = 0 @@ -5498,31 +5980,115 @@ describe('builtin popupmenu', function() screen:try_resize(32, 8) command('set completeopt+=menuone,noselect') feed('i' .. string.rep(' ', 13)) - fn.complete(14, { '哦哦哦哦哦哦哦哦哦哦' }) + + fn.complete(14, { '一二三四五六七八九十' }) if multigrid then screen:expect({ grid = [[ - ## grid 1 - [2:--------------------------------]|*7 - [3:--------------------------------]| - ## grid 2 - ^ | - {1:~ }|*6 - ## grid 3 - {2:-- INSERT --} | - ## grid 4 - {n: 哦哦哦哦哦哦哦哦哦>}| + ## grid 1 + [2:--------------------------------]|*7 + [3:--------------------------------]| + ## grid 2 + ^ | + {1:~ }|*6 + ## grid 3 + {2:-- INSERT --} | + ## grid 4 + {n: 一二三四五六七八九>}| ]], float_pos = { [4] = { -1, 'NW', 2, 1, 12, false, 100 } }, }) else screen:expect([[ ^ | - {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}| + {1:~ }{n: 一二三四五六七八九>}| {1:~ }|*5 {2:-- INSERT --} | ]]) end + feed('<C-E>') + + fn.complete(14, { { word = '一二三', kind = '四五六', menu = '七八九十' } }) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*7 + [3:--------------------------------]| + ## grid 2 + ^ | + {1:~ }|*6 + ## grid 3 + {2:-- INSERT --} | + ## grid 4 + {n: 一二三 四五六 七八>}| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 12, false, 100 } }, + }) + else + screen:expect([[ + ^ | + {1:~ }{n: 一二三 四五六 七八>}| + {1:~ }|*5 + {2:-- INSERT --} | + ]]) + end + feed('<C-E>') + + command('set rightleft') + fn.complete(14, { '一二三四五六七八九十' }) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*7 + [3:--------------------------------]| + ## grid 2 + ^ | + {1: ~}|*6 + ## grid 3 + {2:-- INSERT --} | + ## grid 4 + {n:<九八七六五四三二一 }| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100 } }, + }) + else + screen:expect([[ + ^ | + {n:<九八七六五四三二一 }{1: ~}| + {1: ~}|*5 + {2:-- INSERT --} | + ]]) + end + feed('<C-E>') + + fn.complete(14, { { word = '一二三', kind = '四五六', menu = '七八九十' } }) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*7 + [3:--------------------------------]| + ## grid 2 + ^ | + {1: ~}|*6 + ## grid 3 + {2:-- INSERT --} | + ## grid 4 + {n:<八七 六五四 三二一 }| + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100 } }, + }) + else + screen:expect([[ + ^ | + {n:<八七 六五四 三二一 }{1: ~}| + {1: ~}|*5 + {2:-- INSERT --} | + ]]) + end + feed('<C-E>') end) it('truncates double-width character correctly with scrollbar', function() @@ -5530,37 +6096,140 @@ describe('builtin popupmenu', function() command('set completeopt+=noselect') command('set pumheight=4') feed('i' .. string.rep(' ', 12)) - local items = {} + local items1 = {} + local items2 = {} for _ = 1, 8 do - table.insert(items, { word = '哦哦哦哦哦哦哦哦哦哦', equal = 1, dup = 1 }) + table.insert(items1, { word = '一二三四五六七八九十', equal = 1, dup = 1 }) end - fn.complete(13, items) + for _ = 1, 2 do + table.insert( + items2, + { word = 'abcdef', kind = 'ghijkl', menu = 'mnopqrst', equal = 1, dup = 1 } + ) + end + for _ = 3, 8 do + table.insert( + items2, + { word = '一二三', kind = '四五六', menu = '七八九十', equal = 1, dup = 1 } + ) + end + + fn.complete(13, items1) if multigrid then screen:expect({ grid = [[ - ## grid 1 - [2:--------------------------------]|*7 - [3:--------------------------------]| - ## grid 2 - ^ | - {1:~ }|*6 - ## grid 3 - {2:-- INSERT --} | - ## grid 4 - {n: 哦哦哦哦哦哦哦哦哦>}{c: }|*2 - {n: 哦哦哦哦哦哦哦哦哦>}{s: }|*2 + ## grid 1 + [2:--------------------------------]|*7 + [3:--------------------------------]| + ## grid 2 + ^ | + {1:~ }|*6 + ## grid 3 + {2:-- INSERT --} | + ## grid 4 + {n: 一二三四五六七八九>}{c: }|*2 + {n: 一二三四五六七八九>}{s: }|*2 + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 11, false, 100 } }, + }) + else + screen:expect([[ + ^ | + {1:~ }{n: 一二三四五六七八九>}{c: }|*2 + {1:~ }{n: 一二三四五六七八九>}{s: }|*2 + {1:~ }|*2 + {2:-- INSERT --} | + ]]) + end + feed('<C-E>') + + fn.complete(13, items2) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*7 + [3:--------------------------------]| + ## grid 2 + ^ | + {1:~ }|*6 + ## grid 3 + {2:-- INSERT --} | + ## grid 4 + {n: abcdef ghijkl mnopq}{c: }|*2 + {n: 一二三 四五六 七八>}{s: }|*2 ]], float_pos = { [4] = { -1, 'NW', 2, 1, 11, false, 100 } }, }) else screen:expect([[ ^ | - {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{c: }|*2 - {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{s: }|*2 + {1:~ }{n: abcdef ghijkl mnopq}{c: }|*2 + {1:~ }{n: 一二三 四五六 七八>}{s: }|*2 {1:~ }|*2 {2:-- INSERT --} | ]]) end + feed('<C-E>') + + command('set rightleft') + fn.complete(13, items1) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*7 + [3:--------------------------------]| + ## grid 2 + ^ | + {1: ~}|*6 + ## grid 3 + {2:-- INSERT --} | + ## grid 4 + {c: }{n:<九八七六五四三二一 }|*2 + {s: }{n:<九八七六五四三二一 }|*2 + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100 } }, + }) + else + screen:expect([[ + ^ | + {c: }{n:<九八七六五四三二一 }{1: ~}|*2 + {s: }{n:<九八七六五四三二一 }{1: ~}|*2 + {1: ~}|*2 + {2:-- INSERT --} | + ]]) + end + feed('<C-E>') + + fn.complete(13, items2) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:--------------------------------]|*7 + [3:--------------------------------]| + ## grid 2 + ^ | + {1: ~}|*6 + ## grid 3 + {2:-- INSERT --} | + ## grid 4 + {c: }{n:qponm lkjihg fedcba }|*2 + {s: }{n:<八七 六五四 三二一 }|*2 + ]], + float_pos = { [4] = { -1, 'NW', 2, 1, 0, false, 100 } }, + }) + else + screen:expect([[ + ^ | + {c: }{n:qponm lkjihg fedcba }{1: ~}|*2 + {s: }{n:<八七 六五四 三二一 }{1: ~}|*2 + {1: ~}|*2 + {2:-- INSERT --} | + ]]) + end + feed('<C-E>') end) it('supports mousemodel=popup', function() diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim index 2127945624..fb0af3de0a 100644 --- a/test/old/testdir/gen_opt_test.vim +++ b/test/old/testdir/gen_opt_test.vim @@ -214,10 +214,12 @@ let test_values = { \ 'closeoff', 'hiddenoff', 'foldcolumn:0', 'foldcolumn:12', \ 'followwrap', 'internal', 'indent-heuristic', 'algorithm:myers', \ 'icase,iwhite', 'algorithm:minimal', 'algorithm:patience', - \ 'algorithm:histogram', 'linematch:5'], + \ 'algorithm:histogram', 'inline:none', 'inline:simple', + \ 'inline:char', 'inline:word', 'inline:char,inline:word', 'linematch:5'], \ ['xxx', 'foldcolumn:', 'foldcolumn:x', 'foldcolumn:xxx', \ 'linematch:', 'linematch:x', 'linematch:xxx', 'algorithm:', - \ 'algorithm:xxx', 'context:', 'context:x', 'context:xxx']], + \ 'algorithm:xxx', 'context:', 'context:x', 'context:xxx', + \ 'inline:xxx']], \ 'display': [['', 'lastline', 'truncate', 'uhex', 'lastline,uhex'], \ ['xxx']], \ 'eadirection': [['both', 'ver', 'hor'], ['xxx', 'ver,hor']], diff --git a/test/old/testdir/setup.vim b/test/old/testdir/setup.vim index 87287a57ff..c077eb021a 100644 --- a/test/old/testdir/setup.vim +++ b/test/old/testdir/setup.vim @@ -3,7 +3,7 @@ if exists('s:did_load') set commentstring=/*\ %s\ */ set complete=.,w,b,u,t,i set define=^\\s*#\\s*define - set diffopt=internal,filler,closeoff + set diffopt=internal,filler,closeoff,inline:simple set directory^=. set display= set fillchars=vert:\|,foldsep:\|,fold:- diff --git a/test/old/testdir/test_diffmode.vim b/test/old/testdir/test_diffmode.vim index 0d20085360..6a5054b37b 100644 --- a/test/old/testdir/test_diffmode.vim +++ b/test/old/testdir/test_diffmode.vim @@ -430,13 +430,13 @@ endfunc func Common_icase_test() edit one - call setline(1, ['One', 'Two', 'Three', 'Four', 'Fi#ve']) + call setline(1, ['One', 'Two', 'Three', 'Four', 'Fi#vϵ', 'Si⃗x', 'Se⃗ve⃗n']) redraw let normattr = screenattr(1, 1) diffthis botright vert new two - call setline(1, ['one', 'TWO', 'Three ', 'Four', 'fI=VE']) + call setline(1, ['one', 'TWO', 'Three ', 'Four', 'fI=VΕ', 'SI⃗x', 'SEvE⃗n']) diffthis redraw @@ -444,10 +444,13 @@ func Common_icase_test() call assert_equal(normattr, screenattr(2, 1)) call assert_notequal(normattr, screenattr(3, 1)) call assert_equal(normattr, screenattr(4, 1)) + call assert_equal(normattr, screenattr(6, 2)) + call assert_notequal(normattr, screenattr(7, 2)) let dtextattr = screenattr(5, 3) call assert_notequal(dtextattr, screenattr(5, 1)) call assert_notequal(dtextattr, screenattr(5, 5)) + call assert_notequal(dtextattr, screenattr(7, 4)) diffoff! %bwipe! @@ -779,10 +782,10 @@ endfunc func Test_diff_hlID() new - call setline(1, [1, 2, 3]) + call setline(1, [1, 2, 3, 'Yz', 'a dxxg',]) diffthis vnew - call setline(1, ['1x', 2, 'x', 3]) + call setline(1, ['1x', 2, 'x', 3, 'yx', 'abc defg']) diffthis redraw @@ -793,6 +796,26 @@ func Test_diff_hlID() call diff_hlID(2, 1)->synIDattr("name")->assert_equal("") call diff_hlID(3, 1)->synIDattr("name")->assert_equal("DiffAdd") eval 4->diff_hlID(1)->synIDattr("name")->assert_equal("") + call diff_hlID(5, 1)->synIDattr("name")->assert_equal("DiffText") + call diff_hlID(5, 2)->synIDattr("name")->assert_equal("DiffText") + + set diffopt+=icase " test that caching is invalidated by diffopt change + call diff_hlID(5, 1)->synIDattr("name")->assert_equal("DiffChange") + set diffopt-=icase + call diff_hlID(5, 1)->synIDattr("name")->assert_equal("DiffText") + + call diff_hlID(6, 1)->synIDattr("name")->assert_equal("DiffChange") + call diff_hlID(6, 2)->synIDattr("name")->assert_equal("DiffText") + call diff_hlID(6, 4)->synIDattr("name")->assert_equal("DiffText") + call diff_hlID(6, 7)->synIDattr("name")->assert_equal("DiffText") + call diff_hlID(6, 8)->synIDattr("name")->assert_equal("DiffChange") + set diffopt+=inline:char + call diff_hlID(6, 1)->synIDattr("name")->assert_equal("DiffChange") + call diff_hlID(6, 2)->synIDattr("name")->assert_equal("DiffTextAdd") + call diff_hlID(6, 4)->synIDattr("name")->assert_equal("DiffChange") + call diff_hlID(6, 7)->synIDattr("name")->assert_equal("DiffText") + call diff_hlID(6, 8)->synIDattr("name")->assert_equal("DiffChange") + set diffopt-=inline:char wincmd w call assert_equal(synIDattr(diff_hlID(1, 1), "name"), "DiffChange") @@ -2099,6 +2122,178 @@ func Test_diff_topline_noscroll() call StopVimInTerminal(buf) endfunc +" Test inline highlighting which shows what's different within each diff block +func Test_diff_inline() + CheckScreendump + + call WriteDiffFiles(0, [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2', {}) + call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w") + + call WriteDiffFiles(buf, ["abcdef ghi jk n", "x", "y"], ["aBcef gHi lm n", "y", "z"]) + call VerifyInternal(buf, "Test_diff_inline_01", "") + call VerifyInternal(buf, "Test_diff_inline_02", " diffopt+=inline:none") + + " inline:simple is the same as default + call VerifyInternal(buf, "Test_diff_inline_01", " diffopt+=inline:simple") + + call VerifyInternal(buf, "Test_diff_inline_03", " diffopt+=inline:char") + call VerifyInternal(buf, "Test_diff_inline_04", " diffopt+=inline:word") + + " multiple inline values will the last one + call VerifyInternal(buf, "Test_diff_inline_01", " diffopt+=inline:none,inline:char,inline:simple") + call VerifyInternal(buf, "Test_diff_inline_02", " diffopt+=inline:simple,inline:word,inline:none") + call VerifyInternal(buf, "Test_diff_inline_03", " diffopt+=inline:simple,inline:word,inline:char") + + " DiffTextAdd highlight + call term_sendkeys(buf, ":hi DiffTextAdd ctermbg=blue\<CR>") + call VerifyInternal(buf, "Test_diff_inline_05", " diffopt+=inline:char") + + " Live update in insert mode + call term_sendkeys(buf, "\<Esc>isometext") + call VerifyScreenDump(buf, "Test_diff_inline_06", {}) + call term_sendkeys(buf, "\<Esc>u") + + " icase simple scenarios + call VerifyInternal(buf, "Test_diff_inline_07", " diffopt+=inline:simple,icase") + call VerifyInternal(buf, "Test_diff_inline_08", " diffopt+=inline:char,icase") + call VerifyInternal(buf, "Test_diff_inline_09", " diffopt+=inline:word,icase") + + " diff algorithms should affect highlight + call WriteDiffFiles(buf, ["apples and oranges"], ["oranges and apples"]) + call VerifyInternal(buf, "Test_diff_inline_10", " diffopt+=inline:char") + call VerifyInternal(buf, "Test_diff_inline_11", " diffopt+=inline:char,algorithm:patience") + + " icase: composing chars and Unicode fold case edge cases + call WriteDiffFiles(buf, + \ ["1 - sigma in 6σ and Ὀδυσσεύς", "1 - angstrom in åå", "1 - composing: ii⃗I⃗"], + \ ["2 - Sigma in 6Σ and ὈΔΥΣΣΕΎΣ", "2 - Angstrom in ÅÅ", "2 - Composing: i⃗I⃗I⃗"]) + call VerifyInternal(buf, "Test_diff_inline_12", " diffopt+=inline:char") + call VerifyInternal(buf, "Test_diff_inline_13", " diffopt+=inline:char,icase") + + " wide chars + call WriteDiffFiles(buf, ["abc😅xde一", "f🚀g"], ["abcy😢de", "二f🚀g"]) + call VerifyInternal(buf, "Test_diff_inline_14", " diffopt+=inline:char,icase") + + " NUL char (\n below is internally substituted as NUL) + call WriteDiffFiles(buf, ["1\n34\n5\n6"], ["1234\n5", "6"]) + call VerifyInternal(buf, "Test_diff_inline_15", " diffopt+=inline:char") + + " word diff: always use first buffer's iskeyword and ignore others' for consistency + call WriteDiffFiles(buf, ["foo+bar test"], ["foo+baz test"]) + call VerifyInternal(buf, "Test_diff_inline_word_01", " diffopt+=inline:word") + + call term_sendkeys(buf, ":set iskeyword+=+\<CR>:diffupdate\<CR>") + call VerifyInternal(buf, "Test_diff_inline_word_02", " diffopt+=inline:word") + + call term_sendkeys(buf, ":set iskeyword&\<CR>:wincmd w\<CR>") + call term_sendkeys(buf, ":set iskeyword+=+\<CR>:wincmd w\<CR>:diffupdate\<CR>") + " Use the previous screen dump as 2nd buffer's iskeyword does not matter + call VerifyInternal(buf, "Test_diff_inline_word_01", " diffopt+=inline:word") + + call term_sendkeys(buf, ":windo set iskeyword&\<CR>:1wincmd w\<CR>") + + " char diff: should slide highlight to whitespace boundary if possible for + " better readability (by using forced indent-heuristics). A wrong result + " would be if the highlight is "Bar, prefix". It should be "prefixBar, " + " instead. + call WriteDiffFiles(buf, ["prefixFoo, prefixEnd"], ["prefixFoo, prefixBar, prefixEnd"]) + call VerifyInternal(buf, "Test_diff_inline_char_01", " diffopt+=inline:char") + + " char diff: small gaps between inline diff blocks will be merged during refine step + " - first segment: test that we iteratively merge small gaps after we merged + " adjacent blocks, but only with limited number (set to 4) of iterations. + " - second and third segments: show that we need a large enough adjacent block to + " trigger a merge. + " - fourth segment: small gaps are not merged when adjacent large block is + " on a different line. + call WriteDiffFiles(buf, + \ ["abcdefghijklmno", "anchor1", + \ "abcdefghijklmno", "anchor2", + \ "abcdefghijklmno", "anchor3", + \ "test", "multiline"], + \ ["a?c?e?g?i?k???o", "anchor1", + \ "a??de?????klmno", "anchor2", + \ "a??de??????lmno", "anchor3", + \ "t?s?", "??????i?e"]) + call VerifyInternal(buf, "Test_diff_inline_char_02", " diffopt+=inline:char") + + " Test multi-line blocks and whitespace + call WriteDiffFiles(buf, + \ ["this is ", "sometest text foo", "baz abc def ", "one", "word another word", "additional line"], + \ ["this is some test", "texts", "foo bar abX Yef ", "oneword another word"]) + call VerifyInternal(buf, "Test_diff_inline_multiline_01", " diffopt+=inline:char,iwhite") + call VerifyInternal(buf, "Test_diff_inline_multiline_02", " diffopt+=inline:word,iwhite") + call VerifyInternal(buf, "Test_diff_inline_multiline_03", " diffopt+=inline:char,iwhiteeol") + call VerifyInternal(buf, "Test_diff_inline_multiline_04", " diffopt+=inline:word,iwhiteeol") + call VerifyInternal(buf, "Test_diff_inline_multiline_05", " diffopt+=inline:char,iwhiteall") + call VerifyInternal(buf, "Test_diff_inline_multiline_06", " diffopt+=inline:word,iwhiteall") + + " newline should be highlighted too when 'list' is set + call term_sendkeys(buf, ":windo set list\<CR>") + call VerifyInternal(buf, "Test_diff_inline_multiline_07", " diffopt+=inline:char") + call VerifyInternal(buf, "Test_diff_inline_multiline_08", " diffopt+=inline:char,iwhite") + call VerifyInternal(buf, "Test_diff_inline_multiline_09", " diffopt+=inline:char,iwhiteeol") + call VerifyInternal(buf, "Test_diff_inline_multiline_10", " diffopt+=inline:char,iwhiteall") + call term_sendkeys(buf, ":windo set nolist\<CR>") + + call StopVimInTerminal(buf) +endfunc + +func Test_diff_inline_multibuffer() + CheckScreendump + + call WriteDiffFiles3(0, [], [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2 Xdifile3', {}) + call term_sendkeys(buf, ":windo set autoread\<CR>:1wincmd w\<CR>") + call term_sendkeys(buf, ":hi DiffTextAdd ctermbg=blue\<CR>") + + call WriteDiffFiles3(buf, + \ ["That is buffer1.", "anchor", "Some random text", "anchor"], + \ ["This is buffer2.", "anchor", "Some text", "anchor", "buffer2/3"], + \ ["This is buffer3. Last.", "anchor", "Some more", "text here.", "anchor", "only in buffer2/3", "not in buffer1"]) + call VerifyInternal(buf, "Test_diff_inline_multibuffer_01", " diffopt+=inline:char") + + " Close one of the buffers and make sure it updates correctly + call term_sendkeys(buf, ":diffoff\<CR>") + call VerifyInternal(buf, "Test_diff_inline_multibuffer_02", " diffopt+=inline:char") + + " Update text in the non-diff buffer and nothing should be changed + call term_sendkeys(buf, "\<Esc>isometext") + call VerifyScreenDump(buf, "Test_diff_inline_multibuffer_03", {}) + call term_sendkeys(buf, "\<Esc>u") + + call term_sendkeys(buf, ":diffthis\<CR>") + call VerifyInternal(buf, "Test_diff_inline_multibuffer_01", " diffopt+=inline:char") + + " Test that removing first buffer from diff will in turn use the next + " earliest buffer's iskeyword during word diff. + call WriteDiffFiles3(buf, + \ ["This+is=a-setence"], + \ ["This+is=another-setence"], + \ ["That+is=a-setence"]) + call term_sendkeys(buf, ":set iskeyword+=+\<CR>:2wincmd w\<CR>:set iskeyword+=-\<CR>:1wincmd w\<CR>") + call VerifyInternal(buf, "Test_diff_inline_multibuffer_04", " diffopt+=inline:word") + call term_sendkeys(buf, ":diffoff\<CR>") + call VerifyInternal(buf, "Test_diff_inline_multibuffer_05", " diffopt+=inline:word") + call term_sendkeys(buf, ":diffthis\<CR>") + call VerifyInternal(buf, "Test_diff_inline_multibuffer_04", " diffopt+=inline:word") + + " Test multi-buffer char diff refinement, and that removing a buffer from + " diff will update the others properly. + call WriteDiffFiles3(buf, + \ ["abcdefghijkYmYYY"], + \ ["aXXdXXghijklmnop"], + \ ["abcdefghijkYmYop"]) + call VerifyInternal(buf, "Test_diff_inline_multibuffer_06", " diffopt+=inline:char") + call term_sendkeys(buf, ":diffoff\<CR>") + call VerifyInternal(buf, "Test_diff_inline_multibuffer_07", " diffopt+=inline:char") + call term_sendkeys(buf, ":diffthis\<CR>") + call VerifyInternal(buf, "Test_diff_inline_multibuffer_06", " diffopt+=inline:char") + + call StopVimInTerminal(buf) +endfunc + func Test_diffget_diffput_linematch() CheckScreendump call delete('.Xdifile1.swp') diff --git a/test/old/testdir/test_let.vim b/test/old/testdir/test_let.vim index 22a3a35f87..ec83de880c 100644 --- a/test/old/testdir/test_let.vim +++ b/test/old/testdir/test_let.vim @@ -93,6 +93,26 @@ func Test_let() let [l[0], l[1]] = [10, 20] call assert_equal([10, 20, 3], l) + " Test for using curly brace name in the LHS of an assignment + let listvar = [1, 2] + let s = 'listvar' + let {s} = [3, 4] + call assert_equal([3, 4], listvar) + + " Test for using curly brace name as a list and as list index in the LHS of + " an assignment + let listvar = [1, 2] + let idx = 1 + let s = 'listvar' + let {s}[0] = 10 + let s = 'idx' + let listvar[{s}] = 20 + call assert_equal([10, 20], listvar) + let s1 = 'listvar' + let s2 = 'idx' + let {s1}[{s2}] = 30 + call assert_equal([10, 30], listvar) + " Test for errors in conditional expression call assert_fails('let val = [] ? 1 : 2', 'E745:') call assert_fails('let val = 1 ? 5+ : 6', 'E121:') diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim index 1d9609cbe1..b8011a81e4 100644 --- a/test/old/testdir/test_normal.vim +++ b/test/old/testdir/test_normal.vim @@ -2705,6 +2705,22 @@ func Test_normal33_g_cmd2() call assert_equal(87, col('.')) call assert_equal('E', getreg(0)) + " Have an odd number of chars in the line + norm! A. + call assert_equal(145, col('.')) + norm! gMyl + call assert_equal(73, col('.')) + call assert_equal('0', getreg(0)) + + " 'listchars' "eol" should not affect gM behavior + setlocal list listchars=eol:$ + norm! $ + call assert_equal(145, col('.')) + norm! gMyl + call assert_equal(73, col('.')) + call assert_equal('0', getreg(0)) + setlocal nolist + " Test for gM with Tab characters call setline('.', "\ta\tb\tc\td\te\tf") norm! gMyl diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim index 9104098baa..f3dcb3bbb7 100644 --- a/test/old/testdir/test_options.vim +++ b/test/old/testdir/test_options.vim @@ -313,11 +313,18 @@ func Test_set_completion() call feedkeys(":set suffixes:\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set suffixes:.bak,~,.o,.h,.info,.swp,.obj', @:) - " Expand key codes. + " " Expand key codes. " call feedkeys(":set <H\<C-A>\<C-B>\"\<CR>", 'tx') " call assert_equal('"set <Help> <Home>', @:) - - " Expand terminal options. + " " <BackSpace> (alt name) and <BS> should both show up in auto-complete + " call feedkeys(":set <B\<C-A>\<C-B>\"\<CR>", 'tx') + " call assert_equal('"set <BackSpace> <Bar> <BS> <Bslash>', @:) + " " <ScrollWheelDown> has alt name <MouseUp> but it should not show up here + " " nor show up as duplicates + " call feedkeys(":set <ScrollWheel\<C-A>\<C-B>\"\<CR>", 'tx') + " call assert_equal('"set <ScrollWheelDown> <ScrollWheelLeft> <ScrollWheelRight> <ScrollWheelUp>', @:) + " + " " Expand terminal options. " call feedkeys(":set t_A\<C-A>\<C-B>\"\<CR>", 'tx') " call assert_equal('"set t_AB t_AF t_AU t_AL', @:) " call assert_fails('call feedkeys(":set <t_afoo>=\<C-A>\<CR>", "xt")', 'E474:') @@ -622,10 +629,11 @@ func Test_set_completion_string_values() " call assert_equal([], getcompletion('set completepopup=bogusname:', 'cmdline')) " set previewpopup& completepopup& - " diffopt: special handling of algorithm:<alg_list> + " diffopt: special handling of algorithm:<alg_list> and inline:<inline_type> call assert_equal('filler', getcompletion('set diffopt+=', 'cmdline')[0]) call assert_equal([], getcompletion('set diffopt+=iblank,foldcolumn:', 'cmdline')) call assert_equal('patience', getcompletion('set diffopt+=iblank,algorithm:pat*', 'cmdline')[0]) + call assert_equal('char', getcompletion('set diffopt+=iwhite,inline:ch*', 'cmdline')[0]) " highlight: special parsing, including auto-completing highlight groups " after ':' @@ -715,7 +723,7 @@ func Test_set_completion_string_values() call assert_equal([], getcompletion('set diffopt-=', 'cmdline')) " Test all possible values call assert_equal(['filler', 'context:', 'iblank', 'icase', 'iwhite', 'iwhiteall', 'iwhiteeol', 'horizontal', - \ 'vertical', 'closeoff', 'hiddenoff', 'foldcolumn:', 'followwrap', 'internal', 'indent-heuristic', 'algorithm:', 'linematch:'], + \ 'vertical', 'closeoff', 'hiddenoff', 'foldcolumn:', 'followwrap', 'internal', 'indent-heuristic', 'algorithm:', 'inline:', 'linematch:'], \ getcompletion('set diffopt=', 'cmdline')) set diffopt& diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index 8f81db6213..c5591fac05 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -1992,4 +1992,88 @@ func Test_pum_complete_with_special_characters() call StopVimInTerminal(buf) endfunc +func Test_pum_maxwidth() + CheckScreendump + + let lines =<< trim END + 123456789_123456789_123456789_a + 123456789_123456789_123456789_b + 123 + END + call writefile(lines, 'Xtest', 'D') + let buf = RunVimInTerminal('Xtest', {}) + + call term_sendkeys(buf, "G\"zyy") + call term_sendkeys(buf, "A\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_01', {'rows': 8}) + call term_sendkeys(buf, "\<Esc>3Gdd\"zp") + + call term_sendkeys(buf, ":set pummaxwidth=10\<CR>") + call term_sendkeys(buf, "GA\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_02', {'rows': 8}) + call term_sendkeys(buf, "\<Esc>3Gdd\"zp") + + call term_sendkeys(buf, ":set pummaxwidth=20\<CR>") + call term_sendkeys(buf, "GA\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_03', {'rows': 8}) + call term_sendkeys(buf, "\<Esc>3Gdd\"zp") + + call term_sendkeys(buf, ":set pumwidth=20 pummaxwidth=8\<CR>") + call term_sendkeys(buf, "GA\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_04', {'rows': 8}) + call term_sendkeys(buf, "\<Esc>3Gdd\"zp") + + call term_sendkeys(buf, ":set lines=10 columns=32\<CR>") + call term_sendkeys(buf, "GA\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_09', {'rows': 10, 'cols': 32}) + call term_sendkeys(buf, "\<Esc>3Gdd\"zp") + + call StopVimInTerminal(buf) +endfunc + +func Test_pum_maxwidth_multibyte() + CheckScreendump + + let lines =<< trim END + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [ + \ #{word: "123456789_123456789_123456789_"}, + \ #{word: "一二三四五六七八九十"}, + \ #{word: "abcdefghij"}, + \ #{word: "上下左右"}, + \ ] + endfunc + set omnifunc=Omni_test + END + call writefile(lines, 'Xtest', 'D') + let buf = RunVimInTerminal('-S Xtest', {}) + call TermWait(buf) + + call term_sendkeys(buf, "S\<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_05', {'rows': 8}) + call term_sendkeys(buf, "\<ESC>") + + call term_sendkeys(buf, ":set pummaxwidth=10\<CR>") + call term_sendkeys(buf, "S\<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_06', {'rows': 8}) + call term_sendkeys(buf, "\<ESC>") + + if has('rightleft') + call term_sendkeys(buf, ":set rightleft\<CR>") + call term_sendkeys(buf, "S\<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_07', {'rows': 8}) + call term_sendkeys(buf, "\<Esc>:set norightleft\<CR>") + endif + + call term_sendkeys(buf, ":set pummaxwidth=2\<CR>") + call term_sendkeys(buf, "S\<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_maxwidth_08', {'rows': 8}) + call term_sendkeys(buf, "\<ESC>") + + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_search.vim b/test/old/testdir/test_search.vim index cd36f56f17..4e5cb574bd 100644 --- a/test/old/testdir/test_search.vim +++ b/test/old/testdir/test_search.vim @@ -1499,17 +1499,46 @@ func Test_large_hex_chars2() try /[\Ufffffc1f] catch - call assert_match('E486:', v:exception) + call assert_match('E1541:', v:exception) endtry try set re=1 /[\Ufffffc1f] catch - call assert_match('E486:', v:exception) + call assert_match('E1541:', v:exception) endtry set re& endfunc +func Test_large_hex_chars3() + " Validate max number of Unicode char + try + /[\UFFFFFFFF] + catch + call assert_match('E1541:', v:exception) + endtry + try + /[\UFFFFFFF] + catch + call assert_match('E486:', v:exception) + endtry + try + /\%#=2[\d32-\UFFFFFFFF] + catch + call assert_match('E1541:', v:exception) + endtry + try + /\%#=1[\UFFFFFFFF] + catch + call assert_match('E1541:', v:exception) + endtry + try + /\%#=1[\d32-\UFFFFFFFF] + catch + call assert_match('E945:', v:exception) + endtry +endfunc + func Test_one_error_msg() " This was also giving an internal error call assert_fails('call search(" \\((\\v[[=P=]]){185}+ ")', 'E871:') diff --git a/test/old/testdir/test_shell.vim b/test/old/testdir/test_shell.vim index 9499462a70..e30e9c9ec8 100644 --- a/test/old/testdir/test_shell.vim +++ b/test/old/testdir/test_shell.vim @@ -281,4 +281,18 @@ func Test_shell_no_prevcmd() call delete('Xtestdone') endfunc +func Test_shell_filter_buffer_with_nul_bytes() + CheckUnix + new + set noshelltemp + " \n is a NUL byte + let lines = ["aaa\nbbb\nccc\nddd\neee", "fff\nggg\nhhh\niii\njjj"] + call setline(1, lines) + %!cat + call assert_equal(lines, getline(1, '$')) + + set shelltemp& + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab |