diff options
88 files changed, 2470 insertions, 536 deletions
diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000000..100f97c5f2 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,41 @@ +"lsp": + - runtime/lua/vim/lsp.lua + - runtime/lua/vim/lsp/* + +"lua": + - runtime/lua/**/* + - src/nvim/lua + +"tui": + - src/nvim/tui/tui.* + +"tree-sitter": + - src/nvim/lua/treesitter.* + - runtime/lua/vim/treesitter.lua + - runtime/lua/vim/treesitter/* + +"topic: spell": + - src/nvim/spell* + +"topic: :terminal": + - src/nvim/terminal.* + +"topic: column": + - src/nvim/mark.h + - src/nvim/mark.c + - src/nvim/sign* + +"topic: folds": + - src/nvim/fold* + +"topic: mouse": + - src/nvim/mouse* + +"topic: documentation": + - runtime/doc/* + +"topic: clipboard": + - runtime/autoload/provider/clipboard.vim + +"topic: diff": + - src/nvim/diff.* diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000000..909e197b57 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,13 @@ +name: "Pull Request Labeler" +on: +- pull_request_target +jobs: + triage: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/labeler@main + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" @@ -1,6 +1,6 @@ [](https://neovim.io) -[Documentation](https://neovim.io/doc) | +[Documentation](https://neovim.io/doc/general/) | [Chat](https://gitter.im/neovim/neovim) | [Twitter](https://twitter.com/Neovim) diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index de540405e6..001379c85d 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -423,10 +423,6 @@ function! s:check_python(version) abort \ ' This could lead to confusing error messages.') endif - if a:version == 3 && str2float(pyversion) < 3.3 - call health#report_warn('Python 3.3+ is recommended.') - endif - call health#report_info('Python version: ' . pyversion) if s:is_bad_response(status) diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index 17b6137816..5079c07d8c 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -50,7 +50,7 @@ endfunction function! provider#node#Detect() abort let minver = [6, 0] if exists('g:node_host_prog') - return [expand(g:node_host_prog), ''] + return [expand(g:node_host_prog, v:true), ''] endif if !executable('node') return ['', 'node not found (or not executable)'] diff --git a/runtime/autoload/provider/pythonx.vim b/runtime/autoload/provider/pythonx.vim index 550931d8aa..c292b374b6 100644 --- a/runtime/autoload/provider/pythonx.vim +++ b/runtime/autoload/provider/pythonx.vim @@ -23,7 +23,7 @@ function! provider#pythonx#Require(host) abort endfunction function! s:get_python_executable_from_host_var(major_version) abort - return expand(get(g:, 'python'.(a:major_version == 3 ? '3' : '').'_host_prog', '')) + return expand(get(g:, 'python'.(a:major_version == 3 ? '3' : '').'_host_prog', ''), v:true) endfunction function! s:get_python_candidates(major_version) abort @@ -44,7 +44,7 @@ function! provider#pythonx#DetectByModule(module, major_version) abort let python_exe = s:get_python_executable_from_host_var(a:major_version) if !empty(python_exe) - return [exepath(expand(python_exe)), ''] + return [exepath(expand(python_exe, v:true)), ''] endif let candidates = s:get_python_candidates(a:major_version) diff --git a/runtime/autoload/provider/ruby.vim b/runtime/autoload/provider/ruby.vim index 1f49c623ac..1428fab1cc 100644 --- a/runtime/autoload/provider/ruby.vim +++ b/runtime/autoload/provider/ruby.vim @@ -46,7 +46,7 @@ endfunction function! s:detect() if exists("g:ruby_host_prog") - return expand(g:ruby_host_prog) + return expand(g:ruby_host_prog, v:true) elseif has('win32') return exepath('neovim-ruby-host.bat') else diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index 19a8be1102..b2e910a834 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -1771,7 +1771,7 @@ Vim has a sorting function and a sorting command. The sorting function can be found here: |sort()|, |uniq()|. *:sor* *:sort* -:[range]sor[t][!] [b][f][i][n][o][r][u][x] [/{pattern}/] +:[range]sor[t][!] [b][f][i][l][n][o][r][u][x] [/{pattern}/] Sort lines in [range]. When no range is given all lines are sorted. @@ -1779,6 +1779,16 @@ found here: |sort()|, |uniq()|. With [i] case is ignored. + With [l] sort uses the current collation locale. + Implementation details: strcoll() is used to compare + strings. See |:language| to check or set the collation + locale. Example: > + :language collate en_US.UTF-8 + :%sort l +< |v:collate| can also used to check the current locale. + Sorting using the locale typically ignores case. + This does not work properly on Mac. + Options [n][f][x][o][b] are mutually exclusive. With [n] sorting is done on the first decimal number @@ -1847,8 +1857,7 @@ found here: |sort()|, |uniq()|. Note that using `:sort` with `:global` doesn't sort the matching lines, it's quite useless. -The details about sorting depend on the library function used. There is no -guarantee that sorting obeys the current locale. You will have to try it out. +`:sort` does not use the current locale unless the l flag is used. Vim does do a "stable" sort. The sorting can be interrupted, but if you interrupt it too late in the diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 72c0bec4ff..b19583ed61 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1508,6 +1508,15 @@ v:cmdarg This variable is used for two purposes: the argument for the ":hardcopy" command. This can be used in 'printexpr'. + *v:collate* *collate-variable* +v:collate The current locale setting for collation order of the runtime + environment. This allows Vim scripts to be aware of the + current locale encoding. Technical: it's the value of + LC_COLLATE. When not using a locale the value is "C". + This variable can not be set directly, use the |:language| + command. + See |multi-lang|. + *v:cmdbang* *cmdbang-variable* v:cmdbang Set like v:cmdarg for a file read/write command. When a "!" was used the value is 1, otherwise it is 0. Note that this @@ -2385,6 +2394,7 @@ screenpos({winid}, {lnum}, {col}) Dict screen row and col of a text character screenrow() Number current cursor row search({pattern} [, {flags} [, {stopline} [, {timeout}]]]) Number search for {pattern} +searchcount([{options}]) Dict Get or update the last search count searchdecl({name} [, {global} [, {thisblock}]]) Number search for variable declaration searchpair({start}, {middle}, {end} [, {flags} [, {skip} [...]]]) @@ -2529,6 +2539,8 @@ visualmode([expr]) String last visual mode used wait({timeout}, {condition}[, {interval}]) Number Wait until {condition} is satisfied wildmenumode() Number whether 'wildmenu' mode is active +win_execute({id}, {command} [, {silent}]) + String execute {command} in window {id} win_findbuf({bufnr}) List find windows containing {bufnr} win_getid([{win} [, {tab}]]) Number get |window-ID| for {win} in {tab} win_gettype([{nr}]) String type of window {nr} @@ -3543,6 +3555,8 @@ execute({command} [, {silent}]) *execute()* Note: If nested, an outer execute() will not observe output of the inner calls. Note: Text attributes (highlights) are not captured. + To execute a command in another window than the current one + use `win_execute()`. exepath({expr}) *exepath()* Returns the full path of {expr} if it is an executable and @@ -7280,6 +7294,126 @@ search({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *search()* The 'n' flag tells the function not to move the cursor. +searchcount([{options}]) *searchcount()* + Get or update the last search count, like what is displayed + without the "S" flag in 'shortmess'. This works even if + 'shortmess' does contain the "S" flag. + + This returns a Dictionary. The dictionary is empty if the + previous pattern was not set and "pattern" was not specified. + + key type meaning ~ + current |Number| current position of match; + 0 if the cursor position is + before the first match + exact_match |Boolean| 1 if "current" is matched on + "pos", otherwise 0 + total |Number| total count of matches found + incomplete |Number| 0: search was fully completed + 1: recomputing was timed out + 2: max count exceeded + + For {options} see further down. + + To get the last search count when |n| or |N| was pressed, call + this function with `recompute: 0` . This sometimes returns + wrong information because |n| and |N|'s maximum count is 99. + If it exceeded 99 the result must be max count + 1 (100). If + you want to get correct information, specify `recompute: 1`: > + + " result == maxcount + 1 (100) when many matches + let result = searchcount(#{recompute: 0}) + + " Below returns correct result (recompute defaults + " to 1) + let result = searchcount() +< + The function is useful to add the count to |statusline|: > + function! LastSearchCount() abort + let result = searchcount(#{recompute: 0}) + if empty(result) + return '' + endif + if result.incomplete ==# 1 " timed out + return printf(' /%s [?/??]', @/) + elseif result.incomplete ==# 2 " max count exceeded + if result.total > result.maxcount && + \ result.current > result.maxcount + return printf(' /%s [>%d/>%d]', @/, + \ result.current, result.total) + elseif result.total > result.maxcount + return printf(' /%s [%d/>%d]', @/, + \ result.current, result.total) + endif + endif + return printf(' /%s [%d/%d]', @/, + \ result.current, result.total) + endfunction + let &statusline .= '%{LastSearchCount()}' + + " Or if you want to show the count only when + " 'hlsearch' was on + " let &statusline .= + " \ '%{v:hlsearch ? LastSearchCount() : ""}' +< + You can also update the search count, which can be useful in a + |CursorMoved| or |CursorMovedI| autocommand: > + + autocmd CursorMoved,CursorMovedI * + \ let s:searchcount_timer = timer_start( + \ 200, function('s:update_searchcount')) + function! s:update_searchcount(timer) abort + if a:timer ==# s:searchcount_timer + call searchcount(#{ + \ recompute: 1, maxcount: 0, timeout: 100}) + redrawstatus + endif + endfunction +< + This can also be used to count matched texts with specified + pattern in the current buffer using "pattern": > + + " Count '\<foo\>' in this buffer + " (Note that it also updates search count) + let result = searchcount(#{pattern: '\<foo\>'}) + + " To restore old search count by old pattern, + " search again + call searchcount() +< + {options} must be a Dictionary. It can contain: + key type meaning ~ + recompute |Boolean| if |TRUE|, recompute the count + like |n| or |N| was executed. + otherwise returns the last + result by |n|, |N|, or this + function is returned. + (default: |TRUE|) + pattern |String| recompute if this was given + and different with |@/|. + this works as same as the + below command is executed + before calling this function > + let @/ = pattern +< (default: |@/|) + timeout |Number| 0 or negative number is no + timeout. timeout milliseconds + for recomputing the result + (default: 0) + maxcount |Number| 0 or negative number is no + limit. max count of matched + text while recomputing the + result. if search exceeded + total count, "total" value + becomes `maxcount + 1` + (default: 0) + pos |List| `[lnum, col, off]` value + when recomputing the result. + this changes "current" result + value. see |cursor()|, |getpos() + (default: cursor's position) + + searchdecl({name} [, {global} [, {thisblock}]]) *searchdecl()* Search for the declaration of {name}. @@ -8003,6 +8137,23 @@ sort({list} [, {func} [, {dict}]]) *sort()* *E702* When {func} is given and it is '1' or 'i' then case is ignored. + When {func} is given and it is 'l' then the current collation + locale is used for ordering. Implementation details: strcoll() + is used to compare strings. See |:language| check or set the + collation locale. |v:collate| can also be used to check the + current locale. Sorting using the locale typically ignores + case. Example: > + " ö is sorted similarly to o with English locale. + :language collate en_US.UTF8 + :echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l') +< ['n', 'o', 'O', 'ö', 'p', 'z'] ~ +> + " ö is sorted after z with Swedish locale. + :language collate sv_SE.UTF8 + :echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l') +< ['n', 'o', 'O', 'p', 'z', 'ö'] ~ + This does not work properly on Mac. + When {func} is given and it is 'n' then all items will be sorted numerical (Implementation detail: this uses the strtod() function to parse numbers, Strings, Lists, Dicts and @@ -9123,6 +9274,12 @@ wildmenumode() *wildmenumode()* < (Note, this needs the 'wildcharm' option set appropriately). +win_execute({id}, {command} [, {silent}]) *win_execute()* + Like `execute()` but in the context of window {id}. + The window will temporarily be made the current window, + without triggering autocommands. + Example: > + call win_execute(winid, 'syntax enable') win_findbuf({bufnr}) *win_findbuf()* Returns a |List| with |window-ID|s for windows that contain diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt index 925b3e5dbb..f739e2e88b 100644 --- a/runtime/doc/intro.txt +++ b/runtime/doc/intro.txt @@ -169,6 +169,7 @@ Vim would never have become what it is now, without the help of these people! Ken Takata fixes and features Kazunobu Kuriyama GTK 3 Christian Brabandt many fixes, features, user support, etc. + Yegappan Lakshmanan many quickfix features I wish to thank all the people that sent me bug reports and suggestions. The list is too long to mention them all here. Vim would not be the same without diff --git a/runtime/doc/mlang.txt b/runtime/doc/mlang.txt index b57d2b592a..9d3a51302d 100644 --- a/runtime/doc/mlang.txt +++ b/runtime/doc/mlang.txt @@ -31,6 +31,7 @@ use of "-" and "_". :lan[guage] mes[sages] :lan[guage] cty[pe] :lan[guage] tim[e] +:lan[guage] col[late] Print the current language (aka locale). With the "messages" argument the language used for messages is printed. Technical: LC_MESSAGES. @@ -38,15 +39,19 @@ use of "-" and "_". character encoding is printed. Technical: LC_CTYPE. With the "time" argument the language used for strftime() is printed. Technical: LC_TIME. + With the "collate" argument the language used for + collation order is printed. Technical: LC_COLLATE. Without argument all parts of the locale are printed (this is system dependent). The current language can also be obtained with the - |v:lang|, |v:ctype| and |v:lc_time| variables. + |v:lang|, |v:ctype|, |v:collate| and |v:lc_time| + variables. :lan[guage] {name} :lan[guage] mes[sages] {name} :lan[guage] cty[pe] {name} :lan[guage] tim[e] {name} +:lan[guage] col[late] {name} Set the current language (aka locale) to {name}. The locale {name} must be a valid locale on your system. Some systems accept aliases like "en" or @@ -66,7 +71,10 @@ use of "-" and "_". With the "time" argument the language used for time and date messages is set. This affects strftime(). This sets $LC_TIME. - Without an argument both are set, and additionally + With the "collate" argument the language used for the + collation order is set. This affects sorting of + characters. This sets $LC_COLLATE. + Without an argument all are set, and additionally $LANG is set. The LC_NUMERIC value will always be set to "C" so that floating point numbers use '.' as the decimal diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 269080e750..4a6ae0245b 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -5267,9 +5267,9 @@ A jump table for the options with a short description can be found at |Q_op|. in a file and echoed to the screen. If the 'shell' option is "csh" or "tcsh" after initializations, the default becomes "|& tee". If the 'shell' option is "sh", "ksh", "mksh", "pdksh", "zsh", "zsh-beta", - "bash" or "fish" the default becomes "2>&1| tee". This means that - stderr is also included. Before using the 'shell' option a path is - removed, thus "/bin/sh" uses "sh". + "bash", "fish", "ash" or "dash" the default becomes "2>&1| tee". This + means that stderr is also included. Before using the 'shell' option a + path is removed, thus "/bin/sh" uses "sh". The initialization of this option is done after reading the vimrc and the other initializations, so that when the 'shell' option is set there, the 'shellpipe' option changes automatically, unless it was @@ -5923,6 +5923,18 @@ A jump table for the options with a short description can be found at |Q_op|. Note that there is no '%' before the closing '}'. The expression cannot contain a '}' character, call a function to work around that. See |stl-%{| below. + {% - This is almost same as { except the result of the expression is + re-evaluated as a statusline format string. Thus if the + return value of expr contains % items they will get expanded. + The expression can contain the } character, the end of + expression is denoted by %}. + The For example: > + func! Stl_filename() abort + return "%t" + endfunc +< `stl=%{Stl_filename()}` results in `"%t"` + `stl=%{%Stl_filename()%}` results in `"Name of current file"` + } - End of `{%` expression ( - Start of item group. Can be used for setting the width and alignment of a section. Must be followed by %) somewhere. ) - End of item group. No width fields allowed. diff --git a/runtime/doc/pi_netrw.txt b/runtime/doc/pi_netrw.txt index 5adafd7877..4b61cd4c25 100644 --- a/runtime/doc/pi_netrw.txt +++ b/runtime/doc/pi_netrw.txt @@ -3181,8 +3181,8 @@ window, then the one window will be horizontally split (by default). If there's more than one window, the previous window will be re-used on the selected file/directory. If the previous window's associated buffer has been modified, and there's only one window with that buffer, then -the user will be asked if s/he wishes to save the buffer first (yes, -no, or cancel). +the user will be asked if they wish to save the buffer first (yes, no, or +cancel). Related Actions |netrw-cr| |netrw-o| |netrw-t| |netrw-v| Associated setting variables: diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 95e00720b1..b159f655fa 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -1969,7 +1969,7 @@ LEX *lex.vim* *ft-lex-syntax* Lex uses brute-force synchronizing as the "^%%$" section delimiter gives no clue as to what section follows. Consequently, the value for > :syn sync minlines=300 -may be changed by the user if s/he is experiencing synchronization +may be changed by the user if they are experiencing synchronization difficulties (such as may happen with large lex files). @@ -3009,11 +3009,11 @@ variables in your vimrc: < (dash users should use posix) -If there's no "#! ..." line, and the user hasn't availed himself/herself of a -default sh.vim syntax setting as just shown, then syntax/sh.vim will assume -the Bourne shell syntax. No need to quote RFCs or market penetration -statistics in error reports, please -- just select the default version of the -sh your system uses and install the associated "let..." in your <.vimrc>. +If there's no "#! ..." line, and the user hasn't availed themself of a default +sh.vim syntax setting as just shown, then syntax/sh.vim will assume the Bourne +shell syntax. No need to quote RFCs or market penetration statistics in error +reports, please -- just select the default version of the sh your system uses +and install the associated "let..." in your <.vimrc>. The syntax/sh.vim file provides several levels of syntax-based folding: > diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 1f4b5d3097..39522898f9 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -212,6 +212,11 @@ Here is a list of built-in predicates : ((identifier) @foo (#contains? @foo "foo")) ((identifier) @foo-bar (#contains @foo-bar "foo" "bar")) < + `any-of?` *ts-predicate-any-of?* + Will check if the text is the same as any of the following + arguments : > + ((identifier) @foo (#any-of? @foo "foo" "bar")) +< *lua-treesitter-not-predicate* Each predicate has a `not-` prefixed predicate that is just the negation of the predicate. diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 4cba5a33d0..f92cb3c509 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -1875,7 +1875,7 @@ NOT LOADING It's possible that a user doesn't always want to load this plugin. Or the system administrator has dropped it in the system-wide plugin directory, but a -user has his own plugin he wants to use. Then the user must have a chance to +user has their own plugin they want to use. Then the user must have a chance to disable loading this specific plugin. This will make it possible: > 6 if exists("g:loaded_typecorr") @@ -1908,7 +1908,7 @@ item can be used: > The "<Plug>TypecorrAdd;" thing will do the work, more about that further on. -The user can set the "mapleader" variable to the key sequence that he wants +The user can set the "mapleader" variable to the key sequence that they want this mapping to start with. Thus if the user has done: > let mapleader = "_" @@ -1919,7 +1919,7 @@ will be used, which is a backslash. Then a map for "\a" will be defined. Note that <unique> is used, this will cause an error message if the mapping already happened to exist. |:map-<unique>| -But what if the user wants to define his own key sequence? We can allow that +But what if the user wants to define their own key sequence? We can allow that with this mechanism: > 21 if !hasmapto('<Plug>TypecorrAdd;') @@ -1928,7 +1928,7 @@ with this mechanism: > This checks if a mapping to "<Plug>TypecorrAdd;" already exists, and only defines the mapping from "<Leader>a" if it doesn't. The user then has a -chance of putting this in his vimrc file: > +chance of putting this in their vimrc file: > map ,c <Plug>TypecorrAdd; @@ -2033,7 +2033,7 @@ Now let's add a user command to add a correction: > The user command is defined only if no command with the same name already exists. Otherwise we would get an error here. Overriding the existing user command with ":command!" is not a good idea, this would probably make the user -wonder why the command he defined himself doesn't work. |:command| +wonder why the command they defined themself doesn't work. |:command| SCRIPT VARIABLES @@ -2285,7 +2285,7 @@ An example of how to define functionality in a filetype plugin: > |hasmapto()| is used to check if the user has already defined a map to <Plug>JavaImport;. If not, then the filetype plugin defines the default mapping. This starts with |<LocalLeader>|, which allows the user to select -the key(s) he wants filetype plugin mappings to start with. The default is a +the key(s) they want filetype plugin mappings to start with. The default is a backslash. "<unique>" is used to give an error message if the mapping already exists or overlaps with an existing mapping. diff --git a/runtime/doc/usr_42.txt b/runtime/doc/usr_42.txt index 99da1359c2..ff3ae7057a 100644 --- a/runtime/doc/usr_42.txt +++ b/runtime/doc/usr_42.txt @@ -209,8 +209,8 @@ argument: > :amenu <silent> Mine.Next\ File :call <SID>NextFile()<CR> Don't use "<silent>" too often. It is not needed for short commands. If you -make a menu for someone else, being able to see the executed command will give -him a hint about what he could have typed, instead of using the mouse. +make a menu for someone else, being able to see the executed command will +give them a hint about what they could have typed, instead of using the mouse. LISTING MENUS diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 2c39fdb53c..bb30495d77 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -194,8 +194,7 @@ Options: 'cpoptions' flags: |cpo-_| 'display' flags: "msgsep" minimizes scrolling when showing messages 'guicursor' works in the terminal - 'fillchars' flags: "msgsep" (see 'display'), "foldopen", "foldsep", - "foldclose" + 'fillchars' flags: "msgsep" (see 'display') 'foldcolumn' supports up to 9 dynamic/fixed columns 'inccommand' shows interactive results for |:substitute|-like commands 'pumblend' pseudo-transparent popupmenu diff --git a/runtime/filetype.vim b/runtime/filetype.vim index f0d2b36a84..474a447284 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -1015,6 +1015,7 @@ au BufNewFile,BufRead *.hgrc,*hgrc setf cfg " Meson Build system config au BufNewFile,BufRead meson.build,meson_options.txt setf meson +au BufNewFile,BufRead *.wrap setf dosini " Messages (logs mostly) au BufNewFile,BufRead */log/{auth,cron,daemon,debug,kern,lpr,mail,messages,news/news,syslog,user}{,.log,.err,.info,.warn,.crit,.notice}{,.[0-9]*,-[0-9]*} setf messages diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 341a3e82fc..5dd7109bb0 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -297,26 +297,30 @@ local function pick_call_hierarchy_item(call_hierarchy_items) return choice end +local function call_hierarchy(method) + local params = util.make_position_params() + request('textDocument/prepareCallHierarchy', params, function(err, _, result) + if err then + vim.notify(err.message, vim.log.levels.WARN) + return + end + local call_hierarchy_item = pick_call_hierarchy_item(result) + vim.lsp.buf_request(0, method, { item = call_hierarchy_item }) + end) +end + --- Lists all the call sites of the symbol under the cursor in the --- |quickfix| window. If the symbol can resolve to multiple --- items, the user can pick one in the |inputlist|. function M.incoming_calls() - local params = util.make_position_params() - request('textDocument/prepareCallHierarchy', params, function(_, _, result) - local call_hierarchy_item = pick_call_hierarchy_item(result) - vim.lsp.buf_request(0, 'callHierarchy/incomingCalls', { item = call_hierarchy_item }) - end) + call_hierarchy('callHierarchy/incomingCalls') end --- Lists all the items that are called by the symbol under the --- cursor in the |quickfix| window. If the symbol can resolve to --- multiple items, the user can pick one in the |inputlist|. function M.outgoing_calls() - local params = util.make_position_params() - request('textDocument/prepareCallHierarchy', params, function(_, _, result) - local call_hierarchy_item = pick_call_hierarchy_item(result) - vim.lsp.buf_request(0, 'callHierarchy/outgoingCalls', { item = call_hierarchy_item }) - end) + call_hierarchy('callHierarchy/outgoingCalls') end --- List workspace folders. diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 287624f310..e403a8ee1b 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -916,23 +916,6 @@ function M.make_floating_popup_options(width, height, opts) } end -local function _should_add_to_tagstack(new_item) - local stack = vim.fn.gettagstack() - - -- Check if we're at the bottom of the tagstack. - if stack.curidx <= 1 then return true end - - local top_item = stack.items[stack.curidx-1] - - -- Check if the item at the top of the tagstack is exactly the - -- same as the one we want to push. - if top_item.tagname ~= new_item.tagname then return true end - for i, v in ipairs(top_item.from) do - if v ~= new_item.from[i] then return true end - end - return false -end - --- Jumps to a location. --- --@param location (`Location`|`LocationLink`) @@ -941,36 +924,22 @@ function M.jump_to_location(location) -- location may be Location or LocationLink local uri = location.uri or location.targetUri if uri == nil then return end - - local from_bufnr = vim.fn.bufnr('%') - local from = {from_bufnr, vim.fn.line('.'), vim.fn.col('.'), 0} - local item = {tagname=vim.fn.expand('<cword>'), from=from} - + local bufnr = vim.uri_to_bufnr(uri) -- Save position in jumplist - vim.cmd("mark '") + vim.cmd "normal! m'" + + -- Push a new item into tagstack + local from = {vim.fn.bufnr('%'), vim.fn.line('.'), vim.fn.col('.'), 0} + local items = {{tagname=vim.fn.expand('<cword>'), from=from}} + vim.fn.settagstack(vim.fn.win_getid(), {items=items}, 't') --- Jump to new location (adjusting for UTF-16 encoding of characters) - local bufnr = vim.uri_to_bufnr(uri) api.nvim_set_current_buf(bufnr) api.nvim_buf_set_option(0, 'buflisted', true) local range = location.range or location.targetSelectionRange local row = range.start.line local col = get_line_byte_from_position(0, range.start) - -- This prevents the tagstack to be filled with items that provide - -- no motion when CTRL-T is pressed because they're both the source - -- and the destination. - local motionless = - bufnr == from_bufnr and - row+1 == from[2] and col+1 == from[3] - if not motionless and _should_add_to_tagstack(item) then - local winid = vim.fn.win_getid() - local items = {item} - vim.fn.settagstack(winid, {items=items}, 't') - end - - -- Jump to new location api.nvim_win_set_cursor(0, {row + 1, col}) - return true end @@ -1597,6 +1566,12 @@ function M.make_given_range_params(start_pos, end_pos) if B[2] > 0 then B = {B[1], M.character_offset(0, B[1], B[2])} end + -- we need to offset the end character position otherwise we loose the last + -- character of the selection, as LSP end position is exclusive + -- see https://microsoft.github.io/language-server-protocol/specification#range + if vim.o.selection ~= 'exclusive' then + B[2] = B[2] + 1 + end return { textDocument = M.make_text_document_params(), range = { diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 9b4d28e09a..b81eb18945 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -231,7 +231,7 @@ local predicate_handlers = { local compiled_vim_regexes = setmetatable({}, { __index = function(t, pattern) - local res = vim.regex(check_magic(vim.fn.escape(pattern, '\\'))) + local res = vim.regex(check_magic(pattern)) rawset(t, pattern, res) return res end @@ -260,7 +260,25 @@ local predicate_handlers = { end return false - end + end, + + ["any-of?"] = function(match, _, source, predicate) + local node = match[predicate[2]] + local node_text = M.get_node_text(node, source) + + -- Since 'predicate' will not be used by callers of this function, use it + -- to store a string set built from the list of words to check against. + local string_set = predicate["string_set"] + if not string_set then + string_set = {} + for i=3,#predicate do + string_set[predicate[i]] = true + end + predicate["string_set"] = string_set + end + + return string_set[node_text] + end, } -- As we provide lua-match? also expose vim-match? diff --git a/runtime/tutor/tutor.tutor b/runtime/tutor/tutor.tutor index b46fcc4836..c2d5268e3d 100644 --- a/runtime/tutor/tutor.tutor +++ b/runtime/tutor/tutor.tutor @@ -216,7 +216,7 @@ starting with "$". ## INTERACTIVE ELEMENTS *interactive* As visible in this very document, vim-tutor-mode includes some interactive -elements to provide feedback to the user about his progress. If the text in +elements to provide feedback to the user about their progress. If the text in these elements satisfies some set condition, a ✓ sign will appear in the gutter to the left. Otherwise, a ✗ sign is displayed. diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 8b422b3abe..e4d7115654 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -505,7 +505,7 @@ if(WIN32) set(EXTERNAL_BLOBS_SCRIPT "file(MAKE_DIRECTORY \"${PROJECT_BINARY_DIR}/windows_runtime_deps/platforms\")") foreach(DEP_FILE IN ITEMS - ca-bundle.crt + curl-ca-bundle.crt curl.exe diff.exe tee.exe diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 915b99486d..11a4647d1c 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -27,6 +27,7 @@ #include "nvim/map_defs.h" #include "nvim/map.h" #include "nvim/mark.h" +#include "nvim/ops.h" #include "nvim/extmark.h" #include "nvim/decoration.h" #include "nvim/fileio.h" @@ -441,6 +442,8 @@ void nvim_buf_set_lines(uint64_t channel_id, goto end; } + bcount_t deleted_bytes = get_region_bytecount(curbuf, start, end, 0, 0); + // If the size of the range is reducing (ie, new_len < old_len) we // need to delete some old_len. We do this at the start, by // repeatedly deleting line "start". @@ -460,6 +463,7 @@ void nvim_buf_set_lines(uint64_t channel_id, // new old_len. This is a more efficient operation, as it requires // less memory allocation and freeing. size_t to_replace = old_len < new_len ? old_len : new_len; + bcount_t inserted_bytes = 0; for (size_t i = 0; i < to_replace; i++) { int64_t lnum = start + (int64_t)i; @@ -472,6 +476,8 @@ void nvim_buf_set_lines(uint64_t channel_id, api_set_error(err, kErrorTypeException, "Failed to replace line"); goto end; } + + inserted_bytes += (bcount_t)strlen(lines[i]) + 1; // Mark lines that haven't been passed to the buffer as they need // to be freed later lines[i] = NULL; @@ -491,6 +497,8 @@ void nvim_buf_set_lines(uint64_t channel_id, goto end; } + inserted_bytes += (bcount_t)strlen(lines[i]) + 1; + // Same as with replacing, but we also need to free lines xfree(lines[i]); lines[i] = NULL; @@ -505,7 +513,11 @@ void nvim_buf_set_lines(uint64_t channel_id, (linenr_T)(end - 1), MAXLNUM, (long)extra, - kExtmarkUndo); + kExtmarkNOOP); + + extmark_splice(curbuf, (int)start-1, 0, (int)(end-start), 0, + deleted_bytes, (int)new_len, 0, inserted_bytes, + kExtmarkUndo); changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true); fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra); diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 24ba6110c4..5abdc33709 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -177,42 +177,47 @@ Object dict_get_value(dict_T *dict, String key, Error *err) return vim_to_object(&di->di_tv); } -/// Set a value in a scope dict. Objects are recursively expanded into their -/// vimscript equivalents. -/// -/// @param dict The vimscript dict -/// @param key The key -/// @param value The new value -/// @param del Delete key in place of setting it. Argument `value` is ignored in -/// this case. -/// @param retval If true the old value will be converted and returned. -/// @param[out] err Details of an error that may have occurred -/// @return The old value if `retval` is true and the key was present, else NIL -Object dict_set_var(dict_T *dict, String key, Object value, bool del, - bool retval, Error *err) +dictitem_T *dict_check_writable(dict_T *dict, String key, bool del, Error *err) { - Object rv = OBJECT_INIT; dictitem_T *di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size); if (di != NULL) { if (di->di_flags & DI_FLAGS_RO) { api_set_error(err, kErrorTypeException, "Key is read-only: %s", key.data); - return rv; } else if (di->di_flags & DI_FLAGS_LOCK) { api_set_error(err, kErrorTypeException, "Key is locked: %s", key.data); - return rv; } else if (del && (di->di_flags & DI_FLAGS_FIX)) { api_set_error(err, kErrorTypeException, "Key is fixed: %s", key.data); - return rv; } } else if (dict->dv_lock) { api_set_error(err, kErrorTypeException, "Dictionary is locked"); - return rv; } else if (key.size == 0) { api_set_error(err, kErrorTypeValidation, "Key name is empty"); - return rv; } else if (key.size > INT_MAX) { api_set_error(err, kErrorTypeValidation, "Key name is too long"); + } + + return di; +} + +/// Set a value in a scope dict. Objects are recursively expanded into their +/// vimscript equivalents. +/// +/// @param dict The vimscript dict +/// @param key The key +/// @param value The new value +/// @param del Delete key in place of setting it. Argument `value` is ignored in +/// this case. +/// @param retval If true the old value will be converted and returned. +/// @param[out] err Details of an error that may have occurred +/// @return The old value if `retval` is true and the key was present, else NIL +Object dict_set_var(dict_T *dict, String key, Object value, bool del, + bool retval, Error *err) +{ + Object rv = OBJECT_INIT; + dictitem_T *di = dict_check_writable(dict, key, del, err); + + if (ERROR_SET(err)) { return rv; } @@ -1909,7 +1914,7 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, } else if (strequal(key, "height")) { has_height = true; if (val.type == kObjectTypeInteger && val.data.integer > 0) { - fconfig->height= (int)val.data.integer; + fconfig->height = (int)val.data.integer; } else { api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer"); @@ -1983,6 +1988,14 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, "'focusable' key must be Boolean"); return false; } + } else if (strequal(key, "zindex")) { + if (val.type == kObjectTypeInteger && val.data.integer > 0) { + fconfig->zindex = (int)val.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, + "'zindex' key must be a positive Integer"); + return false; + } } else if (!strcmp(key, "border")) { parse_border_style(val, fconfig, err); if (ERROR_SET(err)) { diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index e934d5dc92..11e21a88ea 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -106,7 +106,8 @@ void win_pos(Integer grid, Window win, Integer startrow, Integer startcol, Integer width, Integer height) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void win_float_pos(Integer grid, Window win, String anchor, Integer anchor_grid, - Float anchor_row, Float anchor_col, Boolean focusable) + Float anchor_row, Float anchor_col, Boolean focusable, + Integer zindex) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void win_external_pos(Integer grid, Window win) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index c363c77afb..e9a0b0df2e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -221,6 +221,12 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) /// in addition the following keys are also recognized: /// `default`: don't override existing definition, /// like `hi default` +/// `ctermfg`: sets foreground of cterm color +/// `ctermbg`: sets background of cterm color +/// `cterm` : cterm attribute map. sets attributed for +/// cterm colors. similer to `hi cterm` +/// Note: by default cterm attributes are +/// same as attributes of gui color /// @param[out] err Error details, if any /// /// TODO: ns_id = 0, should modify :highlight namespace @@ -1411,6 +1417,15 @@ void nvim_chan_send(Integer chan, String data, Error *err) /// - `external`: GUI should display the window as an external /// top-level window. Currently accepts no other positioning /// configuration together with this. +/// - `zindex`: Stacking order. floats with higher `zindex` go on top on +/// floats with lower indices. Must be larger than zero. The +/// following screen elements have hard-coded z-indices: +/// - 100: insert completion popupmenu +/// - 200: message scrollback +/// - 250: cmdline completion popupmenu (when wildoptions+=pum) +/// The default value for floats are 50. In general, values below 100 are +/// recommended, unless there is a good reason to overshadow builtin +/// elements. /// - `style`: Configure the appearance of the window. Currently only takes /// one non-empty value: /// - "minimal" Nvim will display the window with many UI options diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index f942d6b19f..158e149628 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -54,7 +54,7 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err) return; } - if (switch_win(&save_curwin, &save_curtab, win, tab, false) == FAIL) { + if (switch_win_noblock(&save_curwin, &save_curtab, win, tab, false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to switch to window %d", @@ -74,7 +74,7 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err) // So do it now. validate_cursor(); - restore_win(save_curwin, save_curtab, false); + restore_win_noblock(save_curwin, save_curtab, false); } /// Gets the (1,0)-indexed cursor position in the window. |api-indexing| diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index ce4163fccf..6a50264e0f 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -85,6 +85,9 @@ # include "buffer.c.generated.h" #endif +// Determines how deeply nested %{} blocks will be evaluated in statusline. +#define MAX_STL_EVAL_DEPTH 100 + static char *msg_loclist = N_("[Location List]"); static char *msg_qflist = N_("[Quickfix List]"); static char *e_auabort = N_("E855: Autocommands caused command to abort"); @@ -407,7 +410,8 @@ bool buf_valid(buf_T *buf) /// there to be only one window with this buffer. e.g. when /// ":quit" is supposed to close the window but autocommands /// close all other windows. -void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) +/// @returns true when we got to the end and b_nwindows was decremented. +bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) { bool unload_buf = (action != 0); bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE); @@ -444,7 +448,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) // halfway a command that relies on it). Unloading is allowed. if (buf->b_locked > 0 && (del_buf || wipe_buf)) { EMSG(_("E937: Attempt to delete a buffer that is in use")); - return; + return false; } if (win != NULL // Avoid bogus clang warning. @@ -471,13 +475,13 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) buf) && !bufref_valid(&bufref)) { // Autocommands deleted the buffer. EMSG(_(e_auabort)); - return; + return false; } buf->b_locked--; if (abort_if_last && last_nonfloat(win)) { // Autocommands made this the only window. EMSG(_(e_auabort)); - return; + return false; } // When the buffer becomes hidden, but is not unloaded, trigger @@ -488,17 +492,17 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) buf) && !bufref_valid(&bufref)) { // Autocommands deleted the buffer. EMSG(_(e_auabort)); - return; + return false; } buf->b_locked--; if (abort_if_last && last_nonfloat(win)) { // Autocommands made this the only window. EMSG(_(e_auabort)); - return; + return false; } } if (aborting()) { // autocmds may abort script processing - return; + return false; } } @@ -525,7 +529,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) /* Return when a window is displaying the buffer or when it's not * unloaded. */ if (buf->b_nwindows > 0 || !unload_buf) { - return; + return false; } if (buf->terminal) { @@ -561,11 +565,11 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) if (!bufref_valid(&bufref)) { // Autocommands may have deleted the buffer. - return; + return false; } if (aborting()) { // Autocmds may abort script processing. - return; + return false; } /* @@ -576,7 +580,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) * deleted buffer. */ if (buf == curbuf && !is_curbuf) { - return; + return false; } if (win != NULL // Avoid bogus clang warning. @@ -636,6 +640,8 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) buf->b_p_bl = false; } } + // NOTE: at this point "curbuf" may be invalid! + return true; } /// Make buffer not contain a file. @@ -3569,6 +3575,7 @@ int build_stl_str_hl( } int groupdepth = 0; + int evaldepth = 0; int curitem = 0; bool prevchar_isflag = true; @@ -3906,6 +3913,13 @@ int build_stl_str_hl( continue; } + // Denotes end of expanded %{} block + if (*fmt_p == '}' && evaldepth > 0) { + fmt_p++; + evaldepth--; + continue; + } + // An invalid item was specified. // Continue processing on the next character of the format string. if (vim_strchr(STL_ALL, *fmt_p) == NULL) { @@ -3947,18 +3961,30 @@ int build_stl_str_hl( } case STL_VIM_EXPR: // '{' { + char_u *block_start = fmt_p - 1; + int reevaluate = (*fmt_p == '%'); itemisflag = true; + if (reevaluate) { + fmt_p++; + } + // Attempt to copy the expression to evaluate into // the output buffer as a null-terminated string. char_u *t = out_p; - while (*fmt_p != '}' && *fmt_p != NUL && out_p < out_end_p) + while ((*fmt_p != '}' || (reevaluate && fmt_p[-1] != '%')) + && *fmt_p != NUL && out_p < out_end_p) { *out_p++ = *fmt_p++; + } if (*fmt_p != '}') { // missing '}' or out of space break; } fmt_p++; - *out_p = 0; + if (reevaluate) { + out_p[-1] = 0; // remove the % at the end of %{% expr %} + } else { + *out_p = 0; + } // Move our position in the output buffer // to the beginning of the expression @@ -4004,6 +4030,40 @@ int build_stl_str_hl( itemisflag = false; } } + + + // If the output of the expression needs to be evaluated + // replace the %{} block with the result of evaluation + if (reevaluate && str != NULL && *str != 0 + && strchr((const char *)str, '%') != NULL + && evaldepth < MAX_STL_EVAL_DEPTH) { + size_t parsed_usefmt = (size_t)(block_start - usefmt); + size_t str_length = strlen((const char *)str); + size_t fmt_length = strlen((const char *)fmt_p); + size_t new_fmt_len = parsed_usefmt + + str_length + fmt_length + 3; + char_u *new_fmt = (char_u *)xmalloc(new_fmt_len * sizeof(char_u)); + char_u *new_fmt_p = new_fmt; + + new_fmt_p = (char_u *)memcpy(new_fmt_p, usefmt, parsed_usefmt) + + parsed_usefmt; + new_fmt_p = (char_u *)memcpy(new_fmt_p , str, str_length) + + str_length; + new_fmt_p = (char_u *)memcpy(new_fmt_p, "%}", 2) + 2; + new_fmt_p = (char_u *)memcpy(new_fmt_p , fmt_p, fmt_length) + + fmt_length; + *new_fmt_p = 0; + new_fmt_p = NULL; + + if (usefmt != fmt) { + xfree(usefmt); + } + XFREE_CLEAR(str); + usefmt = new_fmt; + fmt_p = usefmt + parsed_usefmt; + evaldepth++; + continue; + } break; } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index dd24db910e..0c839ba12a 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1083,6 +1083,7 @@ typedef struct { FloatRelative relative; bool external; bool focusable; + int zindex; WinStyle style; bool border; bool shadow; @@ -1096,6 +1097,7 @@ typedef struct { .row = 0, .col = 0, .anchor = 0, \ .relative = 0, .external = false, \ .focusable = true, \ + .zindex = kZIndexFloatDefault, \ .style = kWinStyleUnused }) // Structure to store last cursor position and topline. Used by check_lnums() diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 22eb31513d..60af11e94b 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -162,6 +162,7 @@ void channel_init(void) /// Channel is allocated with refcount 1, which should be decreased /// when the underlying stream closes. Channel *channel_alloc(ChannelStreamType type) + FUNC_ATTR_NONNULL_RET { Channel *chan = xcalloc(1, sizeof(*chan)); if (type == kChannelStreamStdio) { diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 56b563cba0..1579f3ff98 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3150,9 +3150,7 @@ static void ins_compl_clear(void) XFREE_CLEAR(compl_orig_text); compl_enter_selects = false; // clear v:completed_item - dict_T *const d = tv_dict_alloc(); - d->dv_lock = VAR_FIXED; - set_vim_var_dict(VV_COMPLETED_ITEM, d); + set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED)); } /// Check that Insert completion is active. @@ -4497,9 +4495,7 @@ static void ins_compl_delete(void) // causes flicker, thus we can't do that. changed_cline_bef_curs(); // clear v:completed_item - dict_T *const d = tv_dict_alloc(); - d->dv_lock = VAR_FIXED; - set_vim_var_dict(VV_COMPLETED_ITEM, d); + set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED)); } // Insert the new text being completed. @@ -4520,8 +4516,7 @@ static void ins_compl_insert(int in_compl_func) static dict_T *ins_compl_dict_alloc(compl_T *match) { // { word, abbr, menu, kind, info } - dict_T *dict = tv_dict_alloc(); - dict->dv_lock = VAR_FIXED; + dict_T *dict = tv_dict_alloc_lock(VAR_FIXED); tv_dict_add_str( dict, S_LEN("word"), (const char *)EMPTY_IF_NULL(match->cp_str)); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 079c0dc3c0..04a9abe41a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -228,6 +228,7 @@ static struct vimvar { VV(VV_EVENT, "event", VAR_DICT, VV_RO), VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), VV(VV_ARGV, "argv", VAR_LIST, VV_RO), + VV(VV_COLLATE, "collate", VAR_STRING, VV_RO), VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), // Neovim VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), @@ -376,11 +377,9 @@ void eval_init(void) msgpack_types_dict->dv_lock = VAR_FIXED; set_vim_var_dict(VV_MSGPACK_TYPES, msgpack_types_dict); - set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc()); + set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED)); - dict_T *v_event = tv_dict_alloc(); - v_event->dv_lock = VAR_FIXED; - set_vim_var_dict(VV_EVENT, v_event); + set_vim_var_dict(VV_EVENT, tv_dict_alloc_lock(VAR_FIXED)); set_vim_var_list(VV_ERRORS, tv_list_alloc(kListLenUnknown)); set_vim_var_nr(VV_STDERR, CHAN_STDERR); set_vim_var_nr(VV_SEARCHFORWARD, 1L); @@ -7616,7 +7615,7 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) /// @param[out] ret_fnum Set to fnum for marks. /// /// @return Pointer to position or NULL in case of error (e.g. invalid type). -pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum, +pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 8188502987..41120b3c78 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -157,6 +157,7 @@ typedef enum { VV_EVENT, VV_ECHOSPACE, VV_ARGV, + VV_COLLATE, VV_EXITING, // Neovim VV_STDERR, diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 77e7c7b3a9..33c6fae5cf 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -217,7 +217,7 @@ return { len={args=1}, libcall={args=3}, libcallnr={args=3}, - line={args=1}, + line={args={1, 2}}, line2byte={args=1}, lispindent={args=1}, list2str={args={1, 2}}, @@ -286,6 +286,7 @@ return { screenpos={args=3}, screenrow={}, search={args={1, 4}}, + searchcount={args={0,1}}, searchdecl={args={1, 3}}, searchpair={args={3, 7}}, searchpairpos={args={3, 7}}, @@ -390,6 +391,7 @@ return { visualmode={args={0, 1}}, wait={args={2,3}}, wildmenumode={}, + win_execute={args={2, 3}}, win_findbuf={args=1}, win_getid={args={0,2}}, win_gettype={args={0,1}}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 60af592108..072d206ecb 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1953,8 +1953,8 @@ static char_u *get_list_line(int c, void *cookie, int indent, bool do_concat) return (char_u *)(s == NULL ? NULL : xstrdup(s)); } -// "execute(command)" function -static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void execute_common(typval_T *argvars, typval_T *rettv, FunPtr fptr, + int arg_off) { const int save_msg_silent = msg_silent; const int save_emsg_silent = emsg_silent; @@ -1968,9 +1968,9 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[arg_off + 1].v_type != VAR_UNKNOWN) { char buf[NUMBUFLEN]; - const char *const s = tv_get_string_buf_chk(&argvars[1], buf); + const char *const s = tv_get_string_buf_chk(&argvars[arg_off + 1], buf); if (s == NULL) { return; @@ -1997,10 +1997,10 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) msg_col = 0; // prevent leading spaces } - if (argvars[0].v_type != VAR_LIST) { - do_cmdline_cmd(tv_get_string(&argvars[0])); - } else if (argvars[0].vval.v_list != NULL) { - list_T *const list = argvars[0].vval.v_list; + if (argvars[arg_off].v_type != VAR_LIST) { + do_cmdline_cmd(tv_get_string(&argvars[arg_off])); + } else if (argvars[arg_off].vval.v_list != NULL) { + list_T *const list = argvars[arg_off].vval.v_list; tv_list_ref(list); GetListLineCookie cookie = { .l = list, @@ -2032,6 +2032,39 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) capture_ga = save_capture_ga; } +// "execute(command)" function +static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + execute_common(argvars, rettv, fptr, 0); +} + +// "win_execute(win_id, command)" function +static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tabpage_T *tp; + win_T *wp = win_id2wp_tp(argvars, &tp); + win_T *save_curwin; + tabpage_T *save_curtab; + // Return an empty string if something fails. + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + if (wp != NULL && tp != NULL) { + pos_T curpos = wp->w_cursor; + if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true) == + OK) { + check_cursor(); + execute_common(argvars, rettv, fptr, 1); + } + restore_win_noblock(save_curwin, save_curtab, true); + + // Update the status line if the cursor moved. + if (win_valid(wp) && !equalpos(curpos, wp->w_cursor)) { + wp->w_redr_status = true; + } + } +} + /// "exepath()" function static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -2737,10 +2770,9 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } else if (strcmp(what, "args") == 0) { rettv->v_type = VAR_LIST; - if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) { - for (int i = 0; i < pt->pt_argc; i++) { - tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]); - } + tv_list_alloc_ret(rettv, pt->pt_argc); + for (int i = 0; i < pt->pt_argc; i++) { + tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]); } } else { EMSG2(_(e_invarg2), what); @@ -5507,18 +5539,36 @@ static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) libcall_common(argvars, rettv, VAR_NUMBER); } -/* - * "line(string)" function - */ +// "line(string, [winid])" function static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T lnum = 0; - pos_T *fp; + pos_T *fp = NULL; int fnum; - fp = var2fpos(&argvars[0], TRUE, &fnum); - if (fp != NULL) + if (argvars[1].v_type != VAR_UNKNOWN) { + tabpage_T *tp; + win_T *save_curwin; + tabpage_T *save_curtab; + + // use window specified in the second argument + win_T *wp = win_id2wp_tp(&argvars[1], &tp); + if (wp != NULL && tp != NULL) { + if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true) + == OK) { + check_cursor(); + fp = var2fpos(&argvars[0], true, &fnum); + } + restore_win_noblock(save_curwin, save_curtab, true); + } + } else { + // use current window + fp = var2fpos(&argvars[0], true, &fnum); + } + + if (fp != NULL) { lnum = fp->lnum; + } rettv->vval.v_number = lnum; } @@ -9181,6 +9231,7 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// struct storing information about current sort typedef struct { int item_compare_ic; + bool item_compare_lc; bool item_compare_numeric; bool item_compare_numbers; bool item_compare_float; @@ -9255,10 +9306,10 @@ static int item_compare(const void *s1, const void *s2, bool keep_zero) p2 = ""; } if (!sortinfo->item_compare_numeric) { - if (sortinfo->item_compare_ic) { - res = STRICMP(p1, p2); + if (sortinfo->item_compare_lc) { + res = strcoll(p1, p2); } else { - res = STRCMP(p1, p2); + res = sortinfo->item_compare_ic ? STRICMP(p1, p2): STRCMP(p1, p2); } } else { double n1, n2; @@ -9393,6 +9444,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) } info.item_compare_ic = false; + info.item_compare_lc = false; info.item_compare_numeric = false; info.item_compare_numbers = false; info.item_compare_float = false; @@ -9437,6 +9489,9 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) } else if (strcmp(info.item_compare_func, "i") == 0) { info.item_compare_func = NULL; info.item_compare_ic = true; + } else if (strcmp(info.item_compare_func, "l") == 0) { + info.item_compare_func = NULL; + info.item_compare_lc = true; } } } diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 71e4edc667..61de83fc21 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2098,7 +2098,7 @@ void tv_dict_set_keys_readonly(dict_T *const dict) /// /// @return [allocated] pointer to the created list. list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { list_T *const l = tv_list_alloc(len); tv_list_set_ret(ret_tv, l); @@ -2106,6 +2106,14 @@ list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len) return l; } +dict_T *tv_dict_alloc_lock(VarLockStatus lock) + FUNC_ATTR_NONNULL_RET +{ + dict_T *const d = tv_dict_alloc(); + d->dv_lock = lock; + return d; +} + /// Allocate an empty dictionary for a return value /// /// Also sets reference count. @@ -2114,9 +2122,8 @@ list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len) void tv_dict_alloc_ret(typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { - dict_T *const d = tv_dict_alloc(); + dict_T *const d = tv_dict_alloc_lock(VAR_UNLOCKED); tv_dict_set_ret(ret_tv, d); - ret_tv->v_lock = VAR_UNLOCKED; } //{{{3 Clear diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 3e330b88a2..6a0a08eee8 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -358,6 +358,7 @@ static int linelen(int *has_tab) static char_u *sortbuf1; static char_u *sortbuf2; +static int sort_lc; ///< sort using locale static int sort_ic; ///< ignore case static int sort_nr; ///< sort on number static int sort_rx; ///< sort on regex instead of skipping it @@ -381,6 +382,13 @@ typedef struct { } st_u; } sorti_T; +static int string_compare(const void *s1, const void *s2) FUNC_ATTR_NONNULL_ALL +{ + if (sort_lc) { + return strcoll((char *)s1, (char *)s2); + } + return sort_ic ? STRICMP(s1, s2) : STRCMP(s1, s2); +} static int sort_compare(const void *s1, const void *s2) { @@ -424,8 +432,7 @@ static int sort_compare(const void *s1, const void *s2) l2.st_u.line.end_col_nr - l2.st_u.line.start_col_nr + 1); sortbuf2[l2.st_u.line.end_col_nr - l2.st_u.line.start_col_nr] = NUL; - result = sort_ic ? STRICMP(sortbuf1, sortbuf2) - : STRCMP(sortbuf1, sortbuf2); + result = string_compare(sortbuf1, sortbuf2); } /* If two lines have the same value, preserve the original line order. */ @@ -466,7 +473,7 @@ void ex_sort(exarg_T *eap) regmatch.regprog = NULL; sorti_T *nrs = xmalloc(count * sizeof(sorti_T)); - sort_abort = sort_ic = sort_rx = sort_nr = sort_flt = 0; + sort_abort = sort_ic = sort_lc = sort_rx = sort_nr = sort_flt = 0; size_t format_found = 0; bool change_occurred = false; // Buffer contents changed. @@ -474,6 +481,8 @@ void ex_sort(exarg_T *eap) if (ascii_iswhite(*p)) { } else if (*p == 'i') { sort_ic = true; + } else if (*p == 'l') { + sort_lc = true; } else if (*p == 'r') { sort_rx = true; } else if (*p == 'n') { @@ -645,8 +654,7 @@ void ex_sort(exarg_T *eap) s = ml_get(get_lnum); size_t bytelen = STRLEN(s) + 1; // include EOL in bytelen old_count += bytelen; - if (!unique || i == 0 - || (sort_ic ? STRICMP(s, sortbuf1) : STRCMP(s, sortbuf1)) != 0) { + if (!unique || i == 0 || string_compare(s, sortbuf1) != 0) { // Copy the line into a buffer, it may become invalid in // ml_append(). And it's needed for "unique". STRCPY(sortbuf1, s); @@ -2426,21 +2434,25 @@ int do_ecmd( * is returned by buflist_new(), nothing to do here. */ if (buf != curbuf) { - /* - * Be careful: The autocommands may delete any buffer and change - * the current buffer. - * - If the buffer we are going to edit is deleted, give up. - * - If the current buffer is deleted, prefer to load the new - * buffer when loading a buffer is required. This avoids - * loading another buffer which then must be closed again. - * - If we ended up in the new buffer already, need to skip a few - * things, set auto_buf. - */ + const int save_cmdwin_type = cmdwin_type; + + // BufLeave applies to the old buffer. + cmdwin_type = 0; + + // Be careful: The autocommands may delete any buffer and change + // the current buffer. + // - If the buffer we are going to edit is deleted, give up. + // - If the current buffer is deleted, prefer to load the new + // buffer when loading a buffer is required. This avoids + // loading another buffer which then must be closed again. + // - If we ended up in the new buffer already, need to skip a few + // things, set auto_buf. if (buf->b_fname != NULL) { new_name = vim_strsave(buf->b_fname); } set_bufref(&au_new_curbuf, buf); apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf); + cmdwin_type = save_cmdwin_type; if (!bufref_valid(&au_new_curbuf)) { // New buffer has been deleted. delbuf_msg(new_name); // Frees new_name. @@ -2454,6 +2466,7 @@ int do_ecmd( auto_buf = true; } else { win_T *the_curwin = curwin; + buf_T *was_curbuf = curbuf; // Set w_closing to avoid that autocommands close the window. // Set b_locked for the same reason. @@ -2467,9 +2480,10 @@ int do_ecmd( // Close the link to the current buffer. This will set // oldwin->w_buffer to NULL. u_sync(false); - close_buffer(oldwin, curbuf, - (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, - false); + const bool did_decrement = close_buffer( + oldwin, curbuf, + (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, + false); // Autocommands may have closed the window. if (win_valid(the_curwin)) { @@ -2489,6 +2503,14 @@ int do_ecmd( goto theend; } if (buf == curbuf) { // already in new buffer + // close_buffer() has decremented the window count, + // increment it again here and restore w_buffer. + if (did_decrement && buf_valid(was_curbuf)) { + was_curbuf->b_nwindows++; + } + if (win_valid_any_tab(oldwin) && oldwin->w_buffer == NULL) { + oldwin->w_buffer = was_curbuf; + } auto_buf = true; } else { // <VN> We could instead free the synblock diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 7f28c001f9..0a2802397d 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -3621,6 +3621,14 @@ void set_lang_var(void) loc = get_locale_val(LC_TIME); # endif set_vim_var_string(VV_LC_TIME, loc, -1); + +# ifdef HAVE_GET_LOCALE_VAL + loc = get_locale_val(LC_COLLATE); +# else + // setlocale() not supported: use the default value + loc = "C"; +# endif + set_vim_var_string(VV_COLLATE, loc, -1); } #ifdef HAVE_WORKING_LIBINTL @@ -3661,6 +3669,10 @@ void ex_language(exarg_T *eap) what = LC_TIME; name = skipwhite(p); whatstr = "time "; + } else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) { + what = LC_COLLATE; + name = skipwhite(p); + whatstr = "collate "; } } @@ -3705,7 +3717,7 @@ void ex_language(exarg_T *eap) // Reset $LC_ALL, otherwise it would overrule everything. os_setenv("LC_ALL", "", 1); - if (what != LC_TIME) { + if (what != LC_TIME && what != LC_COLLATE) { // Tell gettext() what to translate to. It apparently doesn't // use the currently effective locale. if (what == LC_ALL) { @@ -3720,7 +3732,7 @@ void ex_language(exarg_T *eap) } } - // Set v:lang, v:lc_time and v:ctype to the final result. + // Set v:lang, v:lc_time, v:collate and v:ctype to the final result. set_lang_var(); maketitle(); } @@ -3805,12 +3817,15 @@ char_u *get_lang_arg(expand_T *xp, int idx) if (idx == 2) { return (char_u *)"time"; } + if (idx == 3) { + return (char_u *)"collate"; + } init_locales(); if (locales == NULL) { return NULL; } - return locales[idx - 3]; + return locales[idx - 4]; } /// Function given to ExpandGeneric() to obtain the available locales. diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index ae5c334592..c557bb2438 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -212,7 +212,7 @@ void do_exmode(int improved) while (exmode_active) { /* Check for a ":normal" command and no more characters left. */ if (ex_normal_busy > 0 && typebuf.tb_len == 0) { - exmode_active = FALSE; + exmode_active = 0; break; } msg_scroll = true; @@ -3642,7 +3642,8 @@ const char * set_one_cmd_context( } else { if (strncmp(arg, "messages", p - arg) == 0 || strncmp(arg, "ctype", p - arg) == 0 - || strncmp(arg, "time", p - arg) == 0) { + || strncmp(arg, "time", p - arg) == 0 + || strncmp(arg, "collate", p - arg) == 0) { xp->xp_context = EXPAND_LOCALES; xp->xp_pattern = skipwhite((const char_u *)p); } else { @@ -6519,6 +6520,12 @@ ex_win_close( int need_hide; buf_T *buf = win->w_buffer; + // Never close the autocommand window. + if (win == aucmd_win) { + EMSG(_(e_autocmd_close)); + return; + } + need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1); if (need_hide && !buf_hide(buf) && !forceit) { if ((p_confirm || cmdmod.confirm) && p_write) { @@ -6588,9 +6595,6 @@ static void ex_tabonly(exarg_T *eap) // Repeat this up to a 1000 times, because autocommands may // mess up the lists. for (int done = 0; done < 1000; done++) { - FOR_ALL_TAB_WINDOWS(tp, wp) { - assert(wp != aucmd_win); - } FOR_ALL_TABS(tp) { if (tp->tp_topframe != topframe) { tabpage_close_other(tp, eap->forceit); @@ -7303,7 +7307,8 @@ do_exedit( */ if (exmode_active && (eap->cmdidx == CMD_visual || eap->cmdidx == CMD_view)) { - exmode_active = FALSE; + exmode_active = 0; + ex_pressedreturn = false; if (*eap->arg == NUL) { /* Special case: ":global/pat/visual\NLvi-commands" */ if (global_busy) { diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 53571ec8da..75ed5dc0e5 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -3571,6 +3571,7 @@ static void save_cmdline(struct cmdline_info *ccp) * Restore ccline after it has been saved with save_cmdline(). */ static void restore_cmdline(struct cmdline_info *ccp) + FUNC_ATTR_NONNULL_ALL { ccline = *ccp; } @@ -3580,6 +3581,7 @@ static void restore_cmdline(struct cmdline_info *ccp) * passed to restore_cmdline_alloc() later. */ char_u *save_cmdline_alloc(void) + FUNC_ATTR_NONNULL_RET { struct cmdline_info *p = xmalloc(sizeof(struct cmdline_info)); save_cmdline(p); @@ -3590,6 +3592,7 @@ char_u *save_cmdline_alloc(void) * Restore the command line from the return value of save_cmdline_alloc(). */ void restore_cmdline_alloc(char_u *p) + FUNC_ATTR_NONNULL_ALL { restore_cmdline((struct cmdline_info *)p); xfree(p); @@ -6635,11 +6638,13 @@ static int open_cmdwin(void) wp = curwin; set_bufref(&bufref, curbuf); win_goto(old_curwin); - win_close(wp, true); + if (win_valid(wp) && wp != curwin) { + win_close(wp, true); + } // win_close() may have already wiped the buffer when 'bh' is - // set to 'wipe'. - if (bufref_valid(&bufref)) { + // set to 'wipe', autocommands may have closed other windows + if (bufref_valid(&bufref) && bufref.br_buf != curbuf) { close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false); } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 624b7c93f3..0ce2b586e3 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -987,6 +987,8 @@ EXTERN char_u e_dirnotf[] INIT(= N_( "E919: Directory not found in '%s': \"%s\"")); EXTERN char_u e_au_recursive[] INIT(= N_( "E952: Autocommand caused recursive behavior")); +EXTERN char_u e_autocmd_close[] INIT(= N_( + "E813: Cannot close autocmd window")); EXTERN char_u e_unsupportedoption[] INIT(= N_("E519: Option not supported")); EXTERN char_u e_fnametoolong[] INIT(= N_("E856: Filename too long")); EXTERN char_u e_float_as_string[] INIT(= N_("E806: using Float as a String")); diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index 3b34af46e4..724363674c 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -13,6 +13,15 @@ typedef char_u schar_T[(MAX_MCO+1) * 4 + 1]; typedef int sattr_T; +enum { + kZIndexDefaultGrid = 0, + kZIndexFloatDefault = 50, + kZIndexPopupMenu = 100, + kZIndexMessages = 200, + kZIndexCmdlinePopupMenu = 250, +}; + + /// ScreenGrid represents a resizable rectuangular grid displayed by UI clients. /// /// chars[] contains the UTF-8 text that is currently displayed on the grid. @@ -73,6 +82,9 @@ struct ScreenGrid { // whether the grid can be focused with mouse clicks. bool focusable; + // z-index: the order in the stack of grids. + int zindex; + // Below is state owned by the compositor. Should generally not be set/read // outside this module, except for specific compatibilty hacks @@ -96,7 +108,7 @@ struct ScreenGrid { }; #define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \ - false, 0, 0, NULL, false, true, \ + false, 0, 0, NULL, false, true, 0, \ 0, 0, 0, 0, 0, false } #endif // NVIM_GRID_DEFS_H diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 79801262cb..79e474fa2e 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -806,8 +806,11 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err) { HlAttrs hlattrs = HLATTRS_INIT; - int32_t fg = -1, bg = -1, sp = -1; + int32_t fg = -1, bg = -1, ctermfg = -1, ctermbg = -1, sp = -1; int16_t mask = 0; + int16_t cterm_mask = 0; + bool cterm_mask_provided = false; + for (size_t i = 0; i < dict.size; i++) { char *key = dict.items[i].key.data; Object val = dict.items[i].value; @@ -837,6 +840,25 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err) } } + // Handle cterm attrs + if (strequal(key, "cterm") && val.type == kObjectTypeDictionary) { + cterm_mask_provided = true; + Dictionary cterm_dict = val.data.dictionary; + for (size_t l = 0; l < cterm_dict.size; l++) { + char *cterm_dict_key = cterm_dict.items[l].key.data; + Object cterm_dict_val = cterm_dict.items[l].value; + for (int m = 0; flags[m].name; m++) { + if (strequal(flags[m].name, cterm_dict_key)) { + if (api_object_to_bool(cterm_dict_val, cterm_dict_key, false, + err)) { + cterm_mask |= flags[m].flag; + } + break; + } + } + } + } + struct { const char *name; const char *shortname; @@ -844,6 +866,8 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err) } colors[] = { { "foreground", "fg", &fg }, { "background", "bg", &bg }, + { "ctermfg", NULL, &ctermfg }, + { "ctermbg", NULL, &ctermbg }, { "special", "sp", &sp }, { NULL, NULL, NULL }, }; @@ -867,7 +891,6 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err) } } - if (flags[j].name || colors[k].name) { // handled above } else if (link_id && strequal(key, "link")) { @@ -888,13 +911,22 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err) } } + // apply gui mask as default for cterm mask + if (!cterm_mask_provided) { + cterm_mask = mask; + } if (use_rgb) { hlattrs.rgb_ae_attr = mask; hlattrs.rgb_bg_color = bg; hlattrs.rgb_fg_color = fg; hlattrs.rgb_sp_color = sp; + hlattrs.cterm_bg_color = + ctermbg == -1 ? cterm_normal_bg_color : ctermbg + 1; + hlattrs.cterm_fg_color = + ctermfg == -1 ? cterm_normal_fg_color : ctermfg + 1; + hlattrs.cterm_ae_attr = cterm_mask; } else { - hlattrs.cterm_ae_attr = mask; + hlattrs.cterm_ae_attr = cterm_mask; hlattrs.cterm_bg_color = bg == -1 ? cterm_normal_bg_color : bg + 1; hlattrs.cterm_fg_color = fg == -1 ? cterm_normal_fg_color : fg + 1; } diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index f99a2dd0fe..0a52cc16cb 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -471,6 +471,15 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, &nlua_wait); lua_setfield(lstate, -2, "wait"); + // _getvar + lua_pushcfunction(lstate, &nlua_getvar); + lua_setfield(lstate, -2, "_getvar"); + + // _setvar + lua_pushcfunction(lstate, &nlua_setvar); + lua_setfield(lstate, -2, "_setvar"); + + // vim.loop luv_set_loop(lstate, &main_loop.uv); luv_set_callback(lstate, nlua_luv_cfpcall); @@ -870,6 +879,109 @@ check_err: return request ? 1 : 0; } +static dict_T *nlua_get_var_scope(lua_State *lstate) { + const char *scope = luaL_checkstring(lstate, 1); + handle_T handle = (handle_T)luaL_checkinteger(lstate, 2); + dict_T *dict = NULL; + Error err = ERROR_INIT; + if (strequal(scope, "g")) { + dict = &globvardict; + } else if (strequal(scope, "v")) { + dict = &vimvardict; + } else if (strequal(scope, "b")) { + buf_T *buf = find_buffer_by_handle(handle, &err); + if (buf) { + dict = buf->b_vars; + } + } else if (strequal(scope, "w")) { + win_T *win = find_window_by_handle(handle, &err); + if (win) { + dict = win->w_vars; + } + } else if (strequal(scope, "t")) { + tabpage_T *tabpage = find_tab_by_handle(handle, &err); + if (tabpage) { + dict = tabpage->tp_vars; + } + } else { + luaL_error(lstate, "invalid scope", err.msg); + return NULL; + } + + if (ERROR_SET(&err)) { + luaL_error(lstate, "FAIL: %s", err.msg); + return NULL; + } + return dict; +} + + +static int nlua_getvar(lua_State *lstate) +{ + // non-local return if not found + dict_T *dict = nlua_get_var_scope(lstate); + size_t len; + const char *name = luaL_checklstring(lstate, 3, &len); + + dictitem_T *di = tv_dict_find(dict, name, (ptrdiff_t)len); + if (di == NULL) { + return 0; // nil + } + nlua_push_typval(lstate, &di->di_tv, false); + return 1; +} + +static int nlua_setvar(lua_State *lstate) +{ + // non-local return if not found + dict_T *dict = nlua_get_var_scope(lstate); + String key; + key.data = (char *)luaL_checklstring(lstate, 3, &key.size); + + bool del = (lua_gettop(lstate) < 4) || lua_isnil(lstate, 4); + + Error err = ERROR_INIT; + dictitem_T *di = dict_check_writable(dict, key, del, &err); + if (ERROR_SET(&err)) { + return 0; + } + + if (del) { + // Delete the key + if (di == NULL) { + // Doesn't exist, nothing to do + return 0; + } else { + // Delete the entry + tv_dict_item_remove(dict, di); + } + } else { + // Update the key + typval_T tv; + + // Convert the lua value to a vimscript type in the temporary variable + lua_pushvalue(lstate, 4); + if (!nlua_pop_typval(lstate, &tv)) { + return luaL_error(lstate, "Couldn't convert lua value"); + } + + if (di == NULL) { + // Need to create an entry + di = tv_dict_item_alloc_len(key.data, key.size); + tv_dict_add(dict, di); + } else { + // Clear the old value + tv_clear(&di->di_tv); + } + + // Update the value + tv_copy(&tv, &di->di_tv); + // Clear the temporary variable + tv_clear(&tv); + } + return 0; +} + static int nlua_nil_tostring(lua_State *lstate) { lua_pushstring(lstate, "vim.NIL"); diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 3994c5bc5b..bc0770da31 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -341,32 +341,24 @@ do end return setmetatable({}, mt) end - local function pcall_ret(status, ...) - if status then return ... end - end - local function nil_wrap(fn) - return function(...) - return pcall_ret(pcall(fn, ...)) + local function make_dict_accessor(scope) + validate { + scope = {scope, 's'}; + } + local mt = {} + function mt:__newindex(k, v) + return vim._setvar(scope, 0, k, v) end + function mt:__index(k) + return vim._getvar(scope, 0, k) + end + return setmetatable({}, mt) end - - vim.b = make_meta_accessor( - nil_wrap(function(v) return a.nvim_buf_get_var(0, v) end), - function(v, k) return a.nvim_buf_set_var(0, v, k) end, - function(v) return a.nvim_buf_del_var(0, v) end - ) - vim.w = make_meta_accessor( - nil_wrap(function(v) return a.nvim_win_get_var(0, v) end), - function(v, k) return a.nvim_win_set_var(0, v, k) end, - function(v) return a.nvim_win_del_var(0, v) end - ) - vim.t = make_meta_accessor( - nil_wrap(function(v) return a.nvim_tabpage_get_var(0, v) end), - function(v, k) return a.nvim_tabpage_set_var(0, v, k) end, - function(v) return a.nvim_tabpage_del_var(0, v) end - ) - vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var) - vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar) + vim.g = make_dict_accessor('g') + vim.v = make_dict_accessor('v') + vim.b = make_dict_accessor('b') + vim.w = make_dict_accessor('w') + vim.t = make_dict_accessor('t') vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option) local function getenv(k) @@ -522,6 +514,8 @@ function vim.notify(msg, log_level, _opts) if log_level == vim.log.levels.ERROR then vim.api.nvim_err_writeln(msg) + elseif log_level == vim.log.levels.WARN then + vim.api.nvim_echo({{msg, 'WarningMsg'}}, true, {}) else vim.api.nvim_echo({{msg}}, true, {}) end diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 303722b4a8..eb9357d027 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -238,5 +238,6 @@ # define PRAGMA_DIAG_POP #endif +#define EMPTY_POS(a) ((a).lnum == 0 && (a).col == 0 && (a).coladd == 0) #endif // NVIM_MACROS_H diff --git a/src/nvim/message.c b/src/nvim/message.c index a34b895033..ec5dabbbc0 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -165,6 +165,7 @@ void msg_grid_validate(void) // TODO(bfredl): eventually should be set to "invalid". I e all callers // will use the grid including clear to EOS if necessary. grid_alloc(&msg_grid, Rows, Columns, false, true); + msg_grid.zindex = kZIndexMessages; xfree(msg_grid.dirty_col); msg_grid.dirty_col = xcalloc(Rows, sizeof(*msg_grid.dirty_col)); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 54d73b6ae7..0ed116c17f 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1676,17 +1676,14 @@ int op_delete(oparg_T *oap) curbuf_splice_pending++; pos_T startpos = curwin->w_cursor; // start position for delete - bcount_t deleted_bytes = (bcount_t)STRLEN( - ml_get(startpos.lnum)) + 1 - startpos.col; + bcount_t deleted_bytes = get_region_bytecount( + curbuf, startpos.lnum, oap->end.lnum, startpos.col, + oap->end.col) + oap->inclusive; truncate_line(true); // delete from cursor to end of line curpos = curwin->w_cursor; // remember curwin->w_cursor curwin->w_cursor.lnum++; - for (linenr_T i = 1; i <= oap->line_count - 2; i++) { - deleted_bytes += (bcount_t)STRLEN( - ml_get(startpos.lnum + i)) + 1; - } del_lines(oap->line_count - 2, false); // delete from start of line until op_end @@ -1694,7 +1691,6 @@ int op_delete(oparg_T *oap) curwin->w_cursor.col = 0; (void)del_bytes((colnr_T)n, !virtual_op, oap->op_type == OP_DELETE && !oap->is_VIsual); - deleted_bytes += n; curwin->w_cursor = curpos; // restore curwin->w_cursor (void)do_join(2, false, false, false, false); curbuf_splice_pending--; @@ -3334,6 +3330,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) changed_cline_bef_curs(); curwin->w_cursor.col += (colnr_T)(totlen - 1); } + changed_bytes(lnum, col); + extmark_splice_cols(curbuf, (int)lnum-1, col, + 0, (int)totlen, kExtmarkUndo); } if (VIsual_active) { lnum++; @@ -3345,12 +3344,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } curbuf->b_op_end = curwin->w_cursor; - /* For "CTRL-O p" in Insert mode, put cursor after last char */ - if (totlen && (restart_edit != 0 || (flags & PUT_CURSEND))) - ++curwin->w_cursor.col; - changed_bytes(lnum, col); - extmark_splice_cols(curbuf, (int)lnum-1, col, - 0, (int)totlen, kExtmarkUndo); + // For "CTRL-O p" in Insert mode, put cursor after last char + if (totlen && (restart_edit != 0 || (flags & PUT_CURSEND))) { + curwin->w_cursor.col++; + } } else { // Insert at least one line. When y_type is kMTCharWise, break the first // line in two. @@ -6302,3 +6299,33 @@ bool op_reg_set_previous(const char name) y_previous = &y_regs[i]; return true; } + +/// Get the byte count of buffer region. End-exclusive. +/// +/// @return number of bytes +bcount_t get_region_bytecount(buf_T *buf, linenr_T start_lnum, + linenr_T end_lnum, colnr_T start_col, + colnr_T end_col) +{ + linenr_T max_lnum = buf->b_ml.ml_line_count; + if (start_lnum > max_lnum) { + return 0; + } + if (start_lnum == end_lnum) { + return end_col - start_col; + } + const char *first = (const char *)ml_get_buf(buf, start_lnum, false); + bcount_t deleted_bytes = (bcount_t)STRLEN(first) - start_col + 1; + + for (linenr_T i = 1; i <= end_lnum-start_lnum-1; i++) { + if (start_lnum + i > max_lnum) { + return deleted_bytes; + } + deleted_bytes += (bcount_t)STRLEN( + ml_get_buf(buf, start_lnum + i, false)) + 1; + } + if (end_lnum > max_lnum) { + return deleted_bytes; + } + return deleted_bytes + end_col; +} diff --git a/src/nvim/ops.h b/src/nvim/ops.h index a8867e02ea..77d6b4435f 100644 --- a/src/nvim/ops.h +++ b/src/nvim/ops.h @@ -6,6 +6,7 @@ #include "nvim/macros.h" #include "nvim/ascii.h" #include "nvim/types.h" +#include "nvim/extmark.h" #include "nvim/eval/typval.h" #include "nvim/os/time.h" #include "nvim/normal.h" // for MotionType and oparg_T diff --git a/src/nvim/option.c b/src/nvim/option.c index a811d749b9..ad481af7fa 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -855,15 +855,18 @@ void set_init_3(void) p_srr = (char_u *)">&"; options[idx_srr].def_val[VI_DEFAULT] = p_srr; } - } else if ( fnamecmp(p, "sh") == 0 - || fnamecmp(p, "ksh") == 0 - || fnamecmp(p, "mksh") == 0 - || fnamecmp(p, "pdksh") == 0 - || fnamecmp(p, "zsh") == 0 - || fnamecmp(p, "zsh-beta") == 0 - || fnamecmp(p, "bash") == 0 - || fnamecmp(p, "fish") == 0 - ) { + } else if (fnamecmp(p, "sh") == 0 + || fnamecmp(p, "ksh") == 0 + || fnamecmp(p, "mksh") == 0 + || fnamecmp(p, "pdksh") == 0 + || fnamecmp(p, "zsh") == 0 + || fnamecmp(p, "zsh-beta") == 0 + || fnamecmp(p, "bash") == 0 + || fnamecmp(p, "fish") == 0 + || fnamecmp(p, "ash") == 0 + || fnamecmp(p, "dash") == 0 + ) { + // Always use POSIX shell style redirection if we reach this if (do_sp) { p_sp = (char_u *)"2>&1| tee"; options[idx_sp].def_val[VI_DEFAULT] = p_sp; @@ -3638,9 +3641,11 @@ char_u *check_stl_option(char_u *s) return illegal_char(errbuf, sizeof(errbuf), *s); } if (*s == '{') { + int reevaluate = (*s == '%'); s++; - while (*s != '}' && *s) + while ((*s != '}' || (reevaluate && s[-1] != '%')) && *s) { s++; + } if (*s != '}') { return (char_u *)N_("E540: Unclosed expression sequence"); } diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 32c9750628..7d452d6797 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -421,6 +421,10 @@ void pum_redraw(void) } grid_assign_handle(&pum_grid); + + pum_grid.zindex = ((State == CMDLINE) + ? kZIndexCmdlinePopupMenu : kZIndexPopupMenu); + bool moved = ui_comp_put_grid(&pum_grid, pum_row, pum_col-col_off, pum_height, grid_width, false, true); bool invalid_grid = moved || pum_invalid; @@ -439,7 +443,7 @@ void pum_redraw(void) int row_off = pum_above ? pum_height : 0; ui_call_win_float_pos(pum_grid.handle, -1, cstr_to_string(anchor), pum_anchor_grid, pum_row-row_off, pum_col-col_off, - false); + false, pum_grid.zindex); } diff --git a/src/nvim/search.c b/src/nvim/search.c index abe05bbd12..82fc0f9d8e 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -46,38 +46,36 @@ #include "nvim/window.h" #include "nvim/os/time.h" - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "search.c.generated.h" #endif -/* - * This file contains various searching-related routines. These fall into - * three groups: - * 1. string searches (for /, ?, n, and N) - * 2. character searches within a single line (for f, F, t, T, etc) - * 3. "other" kinds of searches like the '%' command, and 'word' searches. - */ -/* - * String searches - * - * The string search functions are divided into two levels: - * lowest: searchit(); uses a pos_T for starting position and found match. - * Highest: do_search(); uses curwin->w_cursor; calls searchit(). - * - * The last search pattern is remembered for repeating the same search. - * This pattern is shared between the :g, :s, ? and / commands. - * This is in search_regcomp(). - * - * The actual string matching is done using a heavily modified version of - * Henry Spencer's regular expression library. See regexp.c. - */ +// This file contains various searching-related routines. These fall into +// three groups: +// 1. string searches (for /, ?, n, and N) +// 2. character searches within a single line (for f, F, t, T, etc) +// 3. "other" kinds of searches like the '%' command, and 'word' searches. +// +// +// String searches +// +// The string search functions are divided into two levels: +// lowest: searchit(); uses a pos_T for starting position and found match. +// Highest: do_search(); uses curwin->w_cursor; calls searchit(). +// +// The last search pattern is remembered for repeating the same search. +// This pattern is shared between the :g, :s, ? and / commands. +// This is in search_regcomp(). +// +// The actual string matching is done using a heavily modified version of +// Henry Spencer's regular expression library. See regexp.c. +// +// +// +// Two search patterns are remembered: One for the :substitute command and +// one for other searches. last_idx points to the one that was used the last +// time. -/* - * Two search patterns are remembered: One for the :substitute command and - * one for other searches. last_idx points to the one that was used the last - * time. - */ static struct spat spats[2] = { // Last used search pattern @@ -1002,7 +1000,7 @@ static int first_submatch(regmmatch_T *rp) /* * Highest level string search function. * Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc' - * If 'dirc' is 0: use previous dir. + * If 'dirc' is 0: use previous dir. * If 'pat' is NULL or empty : use previous string. * If 'options & SEARCH_REV' : go in reverse of previous dir. * If 'options & SEARCH_ECHO': echo the search command and handle options @@ -1042,7 +1040,6 @@ int do_search( char_u *msgbuf = NULL; size_t len; bool has_offset = false; -#define SEARCH_STAT_BUF_LEN 12 /* * A line offset is not remembered, this is vi compatible. @@ -1383,11 +1380,14 @@ int do_search( && c != FAIL && !shortmess(SHM_SEARCHCOUNT) && msgbuf != NULL) { - search_stat(dirc, &pos, show_top_bot_msg, msgbuf, - (count != 1 - || has_offset - || (!(fdo_flags & FDO_SEARCH) - && hasFolding(curwin->w_cursor.lnum, NULL, NULL)))); + cmdline_search_stat(dirc, &pos, &curwin->w_cursor, + show_top_bot_msg, msgbuf, + (count != 1 || has_offset + || (!(fdo_flags & FDO_SEARCH) + && hasFolding(curwin->w_cursor.lnum, NULL, + NULL))), + SEARCH_STAT_DEF_MAX_COUNT, + SEARCH_STAT_DEF_TIMEOUT); } // The search command can be followed by a ';' to do another search. @@ -1715,10 +1715,10 @@ static void find_mps_values(int *initc, int *findc, bool *backwards, * '#' look for preprocessor directives * 'R' look for raw string start: R"delim(text)delim" (only backwards) * - * flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#') - * FM_FORWARD search forwards (when initc is '/', '*' or '#') - * FM_BLOCKSTOP stop at start/end of block ({ or } in column 0) - * FM_SKIPCOMM skip comments (not implemented yet!) + * flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#') + * FM_FORWARD search forwards (when initc is '/', '*' or '#') + * FM_BLOCKSTOP stop at start/end of block ({ or } in column 0) + * FM_SKIPCOMM skip comments (not implemented yet!) * * "oap" is only used to set oap->motion_type for a linewise motion, it can be * NULL @@ -3003,7 +3003,7 @@ current_word( /* * If the start is on white space, and white space should be included - * (" word"), or start is not on white space, and white space should + * (" word"), or start is not on white space, and white space should * not be included ("word"), find end of word. */ if ((cls() == 0) == include) { @@ -3012,8 +3012,8 @@ current_word( } else { /* * If the start is not on white space, and white space should be - * included ("word "), or start is on white space and white - * space should not be included (" "), find start of word. + * included ("word "), or start is on white space and white + * space should not be included (" "), find start of word. * If we end up in the first column of the next line (single char * word) back up to end of the line. */ @@ -4347,121 +4347,287 @@ int linewhite(linenr_T lnum) } // Add the search count "[3/19]" to "msgbuf". -// When "recompute" is true Always recompute the numbers. -static void search_stat(int dirc, pos_T *pos, - bool show_top_bot_msg, char_u *msgbuf, bool recompute) +// See update_search_stat() for other arguments. +static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, + bool show_top_bot_msg, char_u *msgbuf, + bool recompute, int maxcount, long timeout) +{ + searchstat_T stat; + + update_search_stat(dirc, pos, cursor_pos, &stat, recompute, maxcount, + timeout); + if (stat.cur > 0) { + char t[SEARCH_STAT_BUF_LEN]; + + if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { + if (stat.incomplete == 1) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); + } else if (stat.cnt > maxcount && stat.cur > maxcount) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]", + maxcount, maxcount); + } else if (stat.cnt > maxcount) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]", + maxcount, stat.cur); + } else { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", + stat.cnt, stat.cur); + } + } else { + if (stat.incomplete == 1) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); + } else if (stat.cnt > maxcount && stat.cur > maxcount) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]", + maxcount, maxcount); + } else if (stat.cnt > maxcount) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]", + stat.cur, maxcount); + } else { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", + stat.cur, stat.cnt); + } + } + + size_t len = strlen(t); + if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) { + memmove(t + 2, t, len); + t[0] = 'W'; + t[1] = ' '; + len += 2; + } + + memmove(msgbuf + STRLEN(msgbuf) - len, t, len); + if (dirc == '?' && stat.cur == maxcount + 1) { + stat.cur = -1; + } + + // keep the message even after redraw, but don't put in history + msg_hist_off = true; + msg_ext_set_kind("search_count"); + give_warning(msgbuf, false); + msg_hist_off = false; + } +} + +// Add the search count information to "stat". +// "stat" must not be NULL. +// When "recompute" is true always recompute the numbers. +// dirc == 0: don't find the next/previous match (only set the result to "stat") +// dirc == '/': find the next match +// dirc == '?': find the previous match +static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, + searchstat_T *stat, bool recompute, int maxcount, + long timeout) { - int save_ws = p_ws; - int wraparound = false; - pos_T p = (*pos); - static pos_T lastpos = { 0, 0, 0 }; + int save_ws = p_ws; + bool wraparound = false; + pos_T p = (*pos); + static pos_T lastpos = { 0, 0, 0 }; static int cur = 0; static int cnt = 0; + static bool exact_match = false; + static int incomplete = 0; + static int last_maxcount = SEARCH_STAT_DEF_MAX_COUNT; static int chgtick = 0; static char_u *lastpat = NULL; static buf_T *lbuf = NULL; - proftime_T start; -#define OUT_OF_TIME 999 + proftime_T start; + memset(stat, 0, sizeof(searchstat_T)); + + if (dirc == 0 && !recompute && !EMPTY_POS(lastpos)) { + stat->cur = cur; + stat->cnt = cnt; + stat->exact_match = exact_match; + stat->incomplete = incomplete; + stat->last_maxcount = last_maxcount; + return; + } + last_maxcount = maxcount; wraparound = ((dirc == '?' && lt(lastpos, p)) || (dirc == '/' && lt(p, lastpos))); // If anything relevant changed the count has to be recomputed. // STRNICMP ignores case, but we should not ignore case. // Unfortunately, there is no STRNICMP function. + // XXX: above comment should be "no MB_STRCMP function" ? if (!(chgtick == buf_get_changedtick(curbuf) && lastpat != NULL // supress clang/NULL passed as nonnull parameter && STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0 && STRLEN(lastpat) == STRLEN(spats[last_idx].pat) - && equalpos(lastpos, curwin->w_cursor) + && equalpos(lastpos, *cursor_pos) && lbuf == curbuf) - || wraparound || cur < 0 || cur > 99 || recompute) { + || wraparound || cur < 0 || (maxcount > 0 && cur > maxcount) + || recompute) { cur = 0; cnt = 0; + exact_match = false; + incomplete = 0; clearpos(&lastpos); lbuf = curbuf; } - if (equalpos(lastpos, curwin->w_cursor) && !wraparound - && (dirc == '/' ? cur < cnt : cur > 0)) { - cur += dirc == '/' ? 1 : -1; + if (equalpos(lastpos, *cursor_pos) && !wraparound + && (dirc == 0 || dirc == '/' ? cur < cnt : cur > 0)) { + cur += dirc == 0 ? 0 : dirc == '/' ? 1 : -1; } else { + bool done_search = false; + pos_T endpos = { 0, 0, 0 }; p_ws = false; - start = profile_setlimit(20L); - while (!got_int && searchit(curwin, curbuf, &lastpos, NULL, + if (timeout > 0) { + start = profile_setlimit(timeout); + } + while (!got_int && searchit(curwin, curbuf, &lastpos, &endpos, FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST, NULL) != FAIL) { + done_search = true; // Stop after passing the time limit. - if (profile_passed_limit(start)) { - cnt = OUT_OF_TIME; - cur = OUT_OF_TIME; + if (timeout > 0 && profile_passed_limit(start)) { + incomplete = 1; break; } cnt++; if (ltoreq(lastpos, p)) { - cur++; + cur = cnt; + if (lt(p, endpos)) { + exact_match = true; + } } fast_breakcheck(); - if (cnt > 99) { + if (maxcount > 0 && cnt > maxcount) { + incomplete = 2; // max count exceeded break; } } if (got_int) { cur = -1; // abort } + if (done_search) { + xfree(lastpat); + lastpat = vim_strsave(spats[last_idx].pat); + chgtick = buf_get_changedtick(curbuf); + lbuf = curbuf; + lastpos = p; + } } - if (cur > 0) { - char t[SEARCH_STAT_BUF_LEN] = ""; - int len; + stat->cur = cur; + stat->cnt = cnt; + stat->exact_match = exact_match; + stat->incomplete = incomplete; + stat->last_maxcount = last_maxcount; + p_ws = save_ws; +} - if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { - if (cur == OUT_OF_TIME) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?\?/?]"); - } else if (cnt > 99 && cur > 99) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]"); - } else if (cnt > 99) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/%d]", cur); - } else { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", cnt, cur); +// "searchcount()" function +void f_searchcount(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + pos_T pos = curwin->w_cursor; + char_u *pattern = NULL; + int maxcount = SEARCH_STAT_DEF_MAX_COUNT; + long timeout = SEARCH_STAT_DEF_TIMEOUT; + bool recompute = true; + searchstat_T stat; + + tv_dict_alloc_ret(rettv); + + if (shortmess(SHM_SEARCHCOUNT)) { // 'shortmess' contains 'S' flag + recompute = true; + } + + if (argvars[0].v_type != VAR_UNKNOWN) { + dict_T *dict; + dictitem_T *di; + listitem_T *li; + bool error = false; + + if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL) { + EMSG(_(e_dictreq)); + return; + } + dict = argvars[0].vval.v_dict; + di = tv_dict_find(dict, (const char *)"timeout", -1); + if (di != NULL) { + timeout = (long)tv_get_number_chk(&di->di_tv, &error); + if (error) { + return; } - } else { - if (cur == OUT_OF_TIME) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); - } else if (cnt > 99 && cur > 99) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]"); - } else if (cnt > 99) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>99]", cur); - } else { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", cur, cnt); + } + di = tv_dict_find(dict, (const char *)"maxcount", -1); + if (di != NULL) { + maxcount = (int)tv_get_number_chk(&di->di_tv, &error); + if (error) { + return; } } - - len = STRLEN(t); - if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) { - memmove(t + 2, t, len); - t[0] = 'W'; - t[1] = ' '; - len += 2; + di = tv_dict_find(dict, (const char *)"recompute", -1); + if (di != NULL) { + recompute = tv_get_number_chk(&di->di_tv, &error); + if (error) { + return; + } } + di = tv_dict_find(dict, (const char *)"pattern", -1); + if (di != NULL) { + pattern = (char_u *)tv_get_string_chk(&di->di_tv); + if (pattern == NULL) { + return; + } + } + di = tv_dict_find(dict, (const char *)"pos", -1); + if (di != NULL) { + if (di->di_tv.v_type != VAR_LIST) { + EMSG2(_(e_invarg2), "pos"); + return; + } + if (tv_list_len(di->di_tv.vval.v_list) != 3) { + EMSG2(_(e_invarg2), "List format should be [lnum, col, off]"); + return; + } + li = tv_list_find(di->di_tv.vval.v_list, 0L); + if (li != NULL) { + pos.lnum = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error); + if (error) { + return; + } + } + li = tv_list_find(di->di_tv.vval.v_list, 1L); + if (li != NULL) { + pos.col = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error) - 1; + if (error) { + return; + } + } + li = tv_list_find(di->di_tv.vval.v_list, 2L); + if (li != NULL) { + pos.coladd = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error); + if (error) { + return; + } + } + } + } - memmove(msgbuf + STRLEN(msgbuf) - len, t, len); - if (dirc == '?' && cur == 100) { - cur = -1; + save_last_search_pattern(); + if (pattern != NULL) { + if (*pattern == NUL) { + goto the_end; } + xfree(spats[last_idx].pat); + spats[last_idx].pat = vim_strsave(pattern); + } + if (spats[last_idx].pat == NULL || *spats[last_idx].pat == NUL) { + goto the_end; // the previous pattern was never defined + } - xfree(lastpat); - lastpat = vim_strsave(spats[last_idx].pat); - chgtick = buf_get_changedtick(curbuf); - lbuf = curbuf; - lastpos = p; + update_search_stat(0, &pos, &pos, &stat, recompute, maxcount, timeout); - // keep the message even after redraw, but don't put in history - msg_hist_off = true; - msg_ext_set_kind("search_count"); - give_warning(msgbuf, false); - msg_hist_off = false; - } - p_ws = save_ws; + tv_dict_add_nr(rettv->vval.v_dict, S_LEN("current"), stat.cur); + tv_dict_add_nr(rettv->vval.v_dict, S_LEN("total"), stat.cnt); + tv_dict_add_nr(rettv->vval.v_dict, S_LEN("exact_match"), stat.exact_match); + tv_dict_add_nr(rettv->vval.v_dict, S_LEN("incomplete"), stat.incomplete); + tv_dict_add_nr(rettv->vval.v_dict, S_LEN("maxcount"), stat.last_maxcount); + +the_end: + restore_last_search_pattern(); } /* diff --git a/src/nvim/search.h b/src/nvim/search.h index 0366aee8a1..98ddaa5eeb 100644 --- a/src/nvim/search.h +++ b/src/nvim/search.h @@ -6,6 +6,7 @@ #include "nvim/vim.h" #include "nvim/buffer_defs.h" +#include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" #include "nvim/normal.h" #include "nvim/os/time.h" @@ -49,6 +50,11 @@ #define RE_BOTH 2 /* save pat in both patterns */ #define RE_LAST 2 /* use last used pattern if "pat" is NULL */ +// Values for searchcount() +#define SEARCH_STAT_DEF_TIMEOUT 40L +#define SEARCH_STAT_DEF_MAX_COUNT 99 +#define SEARCH_STAT_BUF_LEN 12 + /// Structure containing offset definition for the last search pattern /// /// @note Only offset for the last search pattern is used, not for the last @@ -78,6 +84,16 @@ typedef struct { int sa_wrapped; ///< search wrapped around } searchit_arg_T; +typedef struct searchstat +{ + int cur; // current position of found words + int cnt; // total count of found words + int exact_match; // TRUE if matched exactly on specified position + int incomplete; // 0: search was fully completed + // 1: recomputing was timed out + // 2: max count exceeded + int last_maxcount; // the max count of the last search +} searchstat_T; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "search.h.generated.h" diff --git a/src/nvim/spell.c b/src/nvim/spell.c index f6dc3a04a7..d1428b0117 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1677,6 +1677,7 @@ static void int_wordlist_spl(char_u *fname) // Allocate a new slang_T for language "lang". "lang" can be NULL. // Caller must fill "sl_next". slang_T *slang_alloc(char_u *lang) + FUNC_ATTR_NONNULL_RET { slang_T *lp = xcalloc(1, sizeof(slang_T)); diff --git a/src/nvim/tag.c b/src/nvim/tag.c index a6310344e9..f0e48013b2 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1143,7 +1143,6 @@ static int find_tagfunc_tags( typval_T args[4]; typval_T rettv; char_u flagString[4]; - dict_T *d; taggy_T *tag = &curwin->w_tagstack[curwin->w_tagstackidx]; if (*curbuf->b_p_tfu == NUL) { @@ -1156,7 +1155,7 @@ static int find_tagfunc_tags( args[1].vval.v_string = flagString; // create 'info' dict argument - d = tv_dict_alloc(); + dict_T *const d = tv_dict_alloc_lock(VAR_FIXED); if (tag->user_data != NULL) { tv_dict_add_str(d, S_LEN("user_data"), (const char *)tag->user_data); } diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index e50602ccad..b5c50b5894 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -10,6 +10,7 @@ source test_cursor_func.vim source test_ex_equal.vim source test_ex_undo.vim source test_ex_z.vim +source test_ex_mode.vim source test_execute_func.vim source test_expand_func.vim source test_feedkeys.vim diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 5611560b1b..bb84fa498e 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -190,7 +190,6 @@ func Test_autocmd_bufunload_avoiding_SEGV_02() normal! i1 call assert_fails('edit a.txt', 'E517:') - call feedkeys("\<CR>") autocmd! test_autocmd_bufunload augroup! test_autocmd_bufunload @@ -452,6 +451,27 @@ func Test_autocmd_bufwipe_in_SessLoadPost() endfor endfunc +" Using :blast and :ball for many events caused a crash, because b_nwindows was +" not incremented correctly. +func Test_autocmd_blast_badd() + let content =<< trim [CODE] + au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* blast + edit foo1 + au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* ball + edit foo2 + call writefile(['OK'], 'Xerrors') + qall + [CODE] + + call writefile(content, 'XblastBall') + call system(GetVimCommand() .. ' --clean -S XblastBall') + " call assert_match('OK', readfile('Xerrors')->join()) + call assert_match('OK', join(readfile('Xerrors'))) + + call delete('XblastBall') + call delete('Xerrors') +endfunc + " SEGV occurs in older versions. func Test_autocmd_bufwipe_in_SessLoadPost2() tabnew @@ -1949,6 +1969,26 @@ func Test_autocmd_window() %bw! endfunc +" Test for trying to close the tab that has the temporary window for exeucing +" an autocmd. +func Test_close_autocmd_tab() + edit one.txt + tabnew two.txt + augroup aucmd_win_test + au! + au BufEnter * if expand('<afile>') == 'one.txt' | tabfirst | tabonly | endif + augroup END + + call assert_fails('doautoall BufEnter', 'E813:') + + tabonly + augroup aucmd_win_test + au! + augroup END + augroup! aucmd_win_test + %bwipe! +endfunc + func Test_autocmd_closes_window() au BufNew,BufWinLeave * e %e file yyy @@ -1960,4 +2000,13 @@ func Test_autocmd_closes_window() au! BufWinLeave endfunc +func Test_autocmd_closing_cmdwin() + au BufWinLeave * nested q + call assert_fails("norm 7q?\n", 'E855:') + + au! BufWinLeave + new + only +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 489b2477e6..34126b49fa 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -477,7 +477,7 @@ func Test_expand_star_star() call delete('a', 'rf') endfunc -func Test_paste_in_cmdline() +func Test_cmdline_paste() let @a = "def" call feedkeys(":abc \<C-R>a ghi\<C-B>\"\<CR>", 'tx') call assert_equal('"abc def ghi', @:) @@ -517,18 +517,38 @@ func Test_paste_in_cmdline() bwipe! endfunc -func Test_remove_char_in_cmdline() - call feedkeys(":abc def\<S-Left>\<Del>\<C-B>\"\<CR>", 'tx') - call assert_equal('"abc ef', @:) +func Test_cmdline_remove_char() + let encoding_save = &encoding + + " for e in ['utf8', 'latin1'] + for e in ['utf8'] + exe 'set encoding=' . e + + call feedkeys(":abc def\<S-Left>\<Del>\<C-B>\"\<CR>", 'tx') + call assert_equal('"abc ef', @:, e) + + call feedkeys(":abc def\<S-Left>\<BS>\<C-B>\"\<CR>", 'tx') + call assert_equal('"abcdef', @:) + + call feedkeys(":abc def ghi\<S-Left>\<C-W>\<C-B>\"\<CR>", 'tx') + call assert_equal('"abc ghi', @:, e) + + call feedkeys(":abc def\<S-Left>\<C-U>\<C-B>\"\<CR>", 'tx') + call assert_equal('"def', @:, e) + endfor - call feedkeys(":abc def\<S-Left>\<BS>\<C-B>\"\<CR>", 'tx') - call assert_equal('"abcdef', @:) + let &encoding = encoding_save +endfunc - call feedkeys(":abc def ghi\<S-Left>\<C-W>\<C-B>\"\<CR>", 'tx') - call assert_equal('"abc ghi', @:) +func Test_cmdline_keymap_ctrl_hat() + if !has('keymap') + return + endif - call feedkeys(":abc def\<S-Left>\<C-U>\<C-B>\"\<CR>", 'tx') - call assert_equal('"def', @:) + set keymap=esperanto + call feedkeys(":\"Jxauxdo \<C-^>Jxauxdo \<C-^>Jxauxdo\<CR>", 'tx') + call assert_equal('"Jxauxdo Ĵaŭdo Jxauxdo', @:) + set keymap= endfunc func Test_illegal_address1() @@ -615,10 +635,20 @@ func Test_cmdline_complete_bang() endfunc funct Test_cmdline_complete_languages() + let lang = substitute(execute('language time'), '.*"\(.*\)"$', '\1', '') + call assert_equal(lang, v:lc_time) + + let lang = substitute(execute('language ctype'), '.*"\(.*\)"$', '\1', '') + call assert_equal(lang, v:ctype) + + let lang = substitute(execute('language collate'), '.*"\(.*\)"$', '\1', '') + call assert_equal(lang, v:collate) + let lang = substitute(execute('language messages'), '.*"\(.*\)"$', '\1', '') + call assert_equal(lang, v:lang) call feedkeys(":language \<c-a>\<c-b>\"\<cr>", 'tx') - call assert_match('^"language .*\<ctype\>.*\<messages\>.*\<time\>', @:) + call assert_match('^"language .*\<collate\>.*\<ctype\>.*\<messages\>.*\<time\>', @:) if has('unix') " TODO: these tests don't work on Windows. lang appears to be 'C' @@ -633,6 +663,9 @@ funct Test_cmdline_complete_languages() call feedkeys(":language time \<c-a>\<c-b>\"\<cr>", 'tx') call assert_match('^"language .*\<' . lang . '\>', @:) + + call feedkeys(":language collate \<c-a>\<c-b>\"\<cr>", 'tx') + call assert_match('^"language .*\<' . lang . '\>', @:) endif endfunc @@ -850,20 +883,20 @@ func Test_cmdline_overstrike() " Test overstrike in the middle of the command line. call feedkeys(":\"01234\<home>\<right>\<right>ab\<right>\<insert>cd\<enter>", 'xt') - call assert_equal('"0ab1cd4', @:) + call assert_equal('"0ab1cd4', @:, e) " Test overstrike going beyond end of command line. call feedkeys(":\"01234\<home>\<right>\<right>ab\<right>\<insert>cdefgh\<enter>", 'xt') - call assert_equal('"0ab1cdefgh', @:) + call assert_equal('"0ab1cdefgh', @:, e) " Test toggling insert/overstrike a few times. call feedkeys(":\"01234\<home>\<right>ab\<right>\<insert>cd\<right>\<insert>ef\<enter>", 'xt') - call assert_equal('"ab0cd3ef4', @:) + call assert_equal('"ab0cd3ef4', @:, e) endfor " Test overstrike with multi-byte characters. call feedkeys(":\"テキストエディタ\<home>\<right>\<right>ab\<right>\<insert>cd\<enter>", 'xt') - call assert_equal('"テabキcdエディタ', @:) + call assert_equal('"テabキcdエディタ', @:, e) let &encoding = encoding_save endfunc @@ -972,6 +1005,25 @@ func Test_buffers_lastused() bwipeout bufc endfunc +" Test for CmdwinEnter autocmd +func Test_cmdwin_autocmd() + CheckFeature cmdwin + + augroup CmdWin + au! + autocmd BufLeave * if &buftype == '' | update | endif + autocmd CmdwinEnter * startinsert + augroup END + + call assert_fails('call feedkeys("q:xyz\<CR>", "xt")', 'E492:') + call assert_equal('xyz', @:) + + augroup CmdWin + au! + augroup END + augroup! CmdWin +endfunc + func Test_cmdlineclear_tabenter() " See test/functional/legacy/cmdline_spec.lua CheckScreendump @@ -1020,4 +1072,52 @@ func Test_read_shellcmd() endif endfunc +" Test for recalling newer or older cmdline from history with <Up>, <Down>, +" <S-Up>, <S-Down>, <PageUp>, <PageDown>, <C-p>, or <C-n>. +func Test_recalling_cmdline() + CheckFeature cmdline_hist + + let g:cmdlines = [] + cnoremap <Plug>(save-cmdline) <Cmd>let g:cmdlines += [getcmdline()]<CR> + + let histories = [ + \ {'name': 'cmd', 'enter': ':', 'exit': "\<Esc>"}, + \ {'name': 'search', 'enter': '/', 'exit': "\<Esc>"}, + \ {'name': 'expr', 'enter': ":\<C-r>=", 'exit': "\<Esc>\<Esc>"}, + \ {'name': 'input', 'enter': ":call input('')\<CR>", 'exit': "\<CR>"}, + "\ TODO: {'name': 'debug', ...} + \] + let keypairs = [ + \ {'older': "\<Up>", 'newer': "\<Down>", 'prefixmatch': v:true}, + \ {'older': "\<S-Up>", 'newer': "\<S-Down>", 'prefixmatch': v:false}, + \ {'older': "\<PageUp>", 'newer': "\<PageDown>", 'prefixmatch': v:false}, + \ {'older': "\<C-p>", 'newer': "\<C-n>", 'prefixmatch': v:false}, + \] + let prefix = 'vi' + for h in histories + call histadd(h.name, 'vim') + call histadd(h.name, 'virtue') + call histadd(h.name, 'Virgo') + call histadd(h.name, 'vogue') + call histadd(h.name, 'emacs') + for k in keypairs + let g:cmdlines = [] + let keyseqs = h.enter + \ .. prefix + \ .. repeat(k.older .. "\<Plug>(save-cmdline)", 2) + \ .. repeat(k.newer .. "\<Plug>(save-cmdline)", 2) + \ .. h.exit + call feedkeys(keyseqs, 'xt') + call histdel(h.name, -1) " delete the history added by feedkeys above + let expect = k.prefixmatch + \ ? ['virtue', 'vim', 'virtue', prefix] + \ : ['emacs', 'vogue', 'emacs', prefix] + call assert_equal(expect, g:cmdlines) + endfor + endfor + + unlet g:cmdlines + cunmap <Plug>(save-cmdline) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim new file mode 100644 index 0000000000..f70cb261e0 --- /dev/null +++ b/src/nvim/testdir/test_ex_mode.vim @@ -0,0 +1,82 @@ +" Test editing line in Ex mode (see :help Q and :help gQ). + +" Helper function to test editing line in Q Ex mode +func Ex_Q(cmd) + " Is there a simpler way to test editing Ex line? + call feedkeys("Q" + \ .. "let s:test_ex =<< END\<CR>" + \ .. a:cmd .. "\<CR>" + \ .. "END\<CR>" + \ .. "visual\<CR>", 'tx') + return s:test_ex[0] +endfunc + +" Helper function to test editing line in gQ Ex mode +func Ex_gQ(cmd) + call feedkeys("gQ" .. a:cmd .. "\<C-b>\"\<CR>", 'tx') + let ret = @:[1:] " Remove leading quote. + call feedkeys("visual\<CR>", 'tx') + return ret +endfunc + +" Helper function to test editing line with both Q and gQ Ex mode. +func Ex(cmd) + return [Ex_Q(a:cmd), Ex_gQ(a:cmd)] +endfunc + +" Test editing line in Ex mode (both Q and gQ) +func Test_ex_mode() + throw 'skipped: TODO: ' + let encoding_save = &encoding + set sw=2 + + " for e in ['utf8', 'latin1'] + for e in ['utf8'] + exe 'set encoding=' . e + + call assert_equal(['bar', 'bar'], Ex("foo bar\<C-u>bar"), e) + call assert_equal(["1\<C-u>2", "1\<C-u>2"], Ex("1\<C-v>\<C-u>2"), e) + call assert_equal(["1\<C-b>2\<C-e>3", '213'], Ex("1\<C-b>2\<C-e>3"), e) + call assert_equal(['0123', '2013'], Ex("01\<Home>2\<End>3"), e) + call assert_equal(['0123', '0213'], Ex("01\<Left>2\<Right>3"), e) + call assert_equal(['01234', '0342'], Ex("012\<Left>\<Left>\<Insert>3\<Insert>4"), e) + call assert_equal(["foo bar\<C-w>", 'foo '], Ex("foo bar\<C-w>"), e) + call assert_equal(['foo', 'foo'], Ex("fooba\<Del>\<Del>"), e) + call assert_equal(["foo\tbar", 'foobar'], Ex("foo\<Tab>bar"), e) + call assert_equal(["abbrev\t", 'abbreviate'], Ex("abbrev\<Tab>"), e) + call assert_equal([' 1', "1\<C-t>\<C-t>"], Ex("1\<C-t>\<C-t>"), e) + call assert_equal([' 1', "1\<C-t>\<C-t>"], Ex("1\<C-t>\<C-t>\<C-d>"), e) + call assert_equal([' foo', ' foo'], Ex(" foo\<C-d>"), e) + call assert_equal(['foo', ' foo0'], Ex(" foo0\<C-d>"), e) + call assert_equal(['foo', ' foo^'], Ex(" foo^\<C-d>"), e) + endfor + + set sw& + let &encoding = encoding_save +endfunc + +func Test_ex_mode_errors() + " Not allowed to enter ex mode when text is locked + au InsertCharPre <buffer> normal! gQ<CR> + let caught_e523 = 0 + try + call feedkeys("ix\<esc>", 'xt') + catch /^Vim\%((\a\+)\)\=:E523/ " catch E523 + let caught_e523 = 1 + endtry + call assert_equal(1, caught_e523) + au! InsertCharPre + + new + au CmdLineEnter * call ExEnterFunc() + func ExEnterFunc() + + endfunc + call feedkeys("gQvi\r", 'xt') + + au! CmdLineEnter + delfunc ExEnterFunc + quit +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim index 4c7452fe69..ed2bb2c06b 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -54,6 +54,132 @@ func Test_buffers_lastused() bwipeout bufc endfunc +" Test for the :copy command +func Test_copy() + new + + call setline(1, ['L1', 'L2', 'L3', 'L4']) + " copy lines in a range to inside the range + 1,3copy 2 + call assert_equal(['L1', 'L2', 'L1', 'L2', 'L3', 'L3', 'L4'], getline(1, 7)) + + close! +endfunc + +" Test for the :file command +func Test_file_cmd() + call assert_fails('3file', 'E474:') + call assert_fails('0,0file', 'E474:') + call assert_fails('0file abc', 'E474:') +endfunc + +" Test for the :drop command +func Test_drop_cmd() + call writefile(['L1', 'L2'], 'Xfile') + enew | only + drop Xfile + call assert_equal('L2', getline(2)) + " Test for switching to an existing window + below new + drop Xfile + call assert_equal(1, winnr()) + " Test for splitting the current window + enew | only + set modified + drop Xfile + call assert_equal(2, winnr('$')) + " Check for setting the argument list + call assert_equal(['Xfile'], argv()) + enew | only! + call delete('Xfile') +endfunc + +" Test for the :append command +func Test_append_cmd() + new + call setline(1, [' L1']) + call feedkeys(":append\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt') + call assert_equal([' L1', ' L2', ' L3'], getline(1, '$')) + %delete _ + " append after a specific line + call setline(1, [' L1', ' L2', ' L3']) + call feedkeys(":2append\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt') + call assert_equal([' L1', ' L2', ' L4', ' L5', ' L3'], getline(1, '$')) + %delete _ + " append with toggling 'autoindent' + call setline(1, [' L1']) + call feedkeys(":append!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt') + call assert_equal([' L1', ' L2', ' L3'], getline(1, '$')) + call assert_false(&autoindent) + %delete _ + " append with 'autoindent' set and toggling 'autoindent' + set autoindent + call setline(1, [' L1']) + call feedkeys(":append!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt') + call assert_equal([' L1', ' L2', ' L3'], getline(1, '$')) + call assert_true(&autoindent) + set autoindent& + close! +endfunc + +" Test for the :insert command +func Test_insert_cmd() + set noautoindent " test assumes noautoindent, but it's on by default in Nvim + new + call setline(1, [' L1']) + call feedkeys(":insert\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt') + call assert_equal([' L2', ' L3', ' L1'], getline(1, '$')) + %delete _ + " insert before a specific line + call setline(1, [' L1', ' L2', ' L3']) + call feedkeys(":2insert\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt') + call assert_equal([' L1', ' L4', ' L5', ' L2', ' L3'], getline(1, '$')) + %delete _ + " insert with toggling 'autoindent' + call setline(1, [' L1']) + call feedkeys(":insert!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt') + call assert_equal([' L2', ' L3', ' L1'], getline(1, '$')) + call assert_false(&autoindent) + %delete _ + " insert with 'autoindent' set and toggling 'autoindent' + set autoindent + call setline(1, [' L1']) + call feedkeys(":insert!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt') + call assert_equal([' L2', ' L3', ' L1'], getline(1, '$')) + call assert_true(&autoindent) + set autoindent& + close! +endfunc + +" Test for the :change command +func Test_change_cmd() + set noautoindent " test assumes noautoindent, but it's on by default in Nvim + new + call setline(1, [' L1', 'L2', 'L3']) + call feedkeys(":change\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt') + call assert_equal([' L4', ' L5', 'L2', 'L3'], getline(1, '$')) + %delete _ + " change a specific line + call setline(1, [' L1', ' L2', ' L3']) + call feedkeys(":2change\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt') + call assert_equal([' L1', ' L4', ' L5', ' L3'], getline(1, '$')) + %delete _ + " change with toggling 'autoindent' + call setline(1, [' L1', 'L2', 'L3']) + call feedkeys(":change!\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt') + call assert_equal([' L4', ' L5', 'L2', 'L3'], getline(1, '$')) + call assert_false(&autoindent) + %delete _ + " change with 'autoindent' set and toggling 'autoindent' + set autoindent + call setline(1, [' L1', 'L2', 'L3']) + call feedkeys(":change!\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt') + call assert_equal([' L4', ' L5', 'L2', 'L3'], getline(1, '$')) + call assert_true(&autoindent) + set autoindent& + close! +endfunc + " Test for the :confirm command dialog func Test_confirm_cmd() CheckNotGui diff --git a/src/nvim/testdir/test_execute_func.vim b/src/nvim/testdir/test_execute_func.vim index eb84a6739d..15ba894dbe 100644 --- a/src/nvim/testdir/test_execute_func.vim +++ b/src/nvim/testdir/test_execute_func.vim @@ -1,5 +1,7 @@ " test execute() +source view_util.vim + func NestedEval() let nested = execute('echo "nested\nlines"') echo 'got: "' . nested . '"' @@ -82,3 +84,54 @@ func Test_execute_not_silent() endfor call assert_equal('xyz ', text2) endfunc + +func Test_win_execute() + let thiswin = win_getid() + new + let otherwin = win_getid() + call setline(1, 'the new window') + call win_gotoid(thiswin) + let line = win_execute(otherwin, 'echo getline(1)') + call assert_match('the new window', line) + let line = win_execute(134343, 'echo getline(1)') + call assert_equal('', line) + + if has('textprop') + let popupwin = popup_create('the popup win', {'line': 2, 'col': 3}) + redraw + let line = win_execute(popupwin, 'echo getline(1)') + call assert_match('the popup win', line) + + call popup_close(popupwin) + endif + + call win_gotoid(otherwin) + bwipe! +endfunc + +func Test_win_execute_update_ruler() + enew + call setline(1, range(500)) + 20 + split + let winid = win_getid() + set ruler + wincmd w + let height = winheight(winid) + redraw + call assert_match('20,1', Screenline(height + 1)) + let line = win_execute(winid, 'call cursor(100, 1)') + redraw + call assert_match('100,1', Screenline(height + 1)) + + bwipe! +endfunc + +func Test_win_execute_other_tab() + let thiswin = win_getid() + tabnew + call win_execute(thiswin, 'let xyz = 1') + call assert_equal(1, xyz) + tabclose + unlet xyz +endfunc diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 6b1e484c3a..056b953d0b 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -148,7 +148,7 @@ let s:filename_checks = { \ 'dnsmasq': ['/etc/dnsmasq.conf', '/etc/dnsmasq.d/file', 'any/etc/dnsmasq.conf', 'any/etc/dnsmasq.d/file'], \ 'dockerfile': ['Containerfile', 'Dockerfile', 'file.Dockerfile'], \ 'dosbatch': ['file.bat', 'file.sys'], - \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/pacman.conf', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file'], + \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/pacman.conf', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file', 'file.wrap'], \ 'dot': ['file.dot', 'file.gv'], \ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe', 'drac.file', 'lpe', 'lvs', 'some-lpe', 'some-lvs'], \ 'dsl': ['file.dsl'], diff --git a/src/nvim/testdir/test_fnameescape.vim b/src/nvim/testdir/test_fnameescape.vim index 5382b89aa6..0bafdc29fb 100644 --- a/src/nvim/testdir/test_fnameescape.vim +++ b/src/nvim/testdir/test_fnameescape.vim @@ -18,4 +18,10 @@ func Test_fnameescape() endtry call assert_true(status, "ExclamationMark") call delete(fname) + + call assert_equal('\-', fnameescape('-')) + call assert_equal('\+', fnameescape('+')) + call assert_equal('\>', fnameescape('>')) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_ga.vim b/src/nvim/testdir/test_ga.vim index 87f1382342..ce31edfc7a 100644 --- a/src/nvim/testdir/test_ga.vim +++ b/src/nvim/testdir/test_ga.vim @@ -18,6 +18,7 @@ func Test_ga_command() call assert_equal("\nNUL", Do_ga('')) call assert_equal("\n<^A> 1, Hex 01, Oct 001, Digr SH", Do_ga("\x01")) call assert_equal("\n<^I> 9, Hex 09, Oct 011, Digr HT", Do_ga("\t")) + call assert_equal("\n<^@> 0, Hex 00, Octal 000", Do_ga("\n")) call assert_equal("\n<e> 101, Hex 65, Octal 145", Do_ga('e')) @@ -30,5 +31,13 @@ func Test_ga_command() call assert_equal("\n<e> 101, Hex 65, Octal 145 < ́> 769, Hex 0301, Octal 1401", Do_ga("e\u0301")) call assert_equal("\n<e> 101, Hex 65, Octal 145 < ́> 769, Hex 0301, Octal 1401 < ̱> 817, Hex 0331, Octal 1461", Do_ga("e\u0301\u0331")) call assert_equal("\n<e> 101, Hex 65, Octal 145 < ́> 769, Hex 0301, Octal 1401 < ̱> 817, Hex 0331, Octal 1461 < ̸> 824, Hex 0338, Octal 1470", Do_ga("e\u0301\u0331\u0338")) + + " When using Mac fileformat, CR instead of NL is used for line termination + enew! + set fileformat=mac + call assert_equal("\n<^J> 10, Hex 0a, Oct 012, Digr NU", Do_ga("\r")) + bwipe! endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim index 7ccf2812ff..2de2c412de 100644 --- a/src/nvim/testdir/test_global.vim +++ b/src/nvim/testdir/test_global.vim @@ -29,3 +29,11 @@ func Test_nested_global() call assert_equal(['nothing', '++found', 'found bad', 'bad'], getline(1, 4)) bwipe! endfunc + +func Test_global_error() + call assert_fails('g\\a', 'E10:') + call assert_fails('g', 'E148:') + call assert_fails('g/\(/y', 'E476:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_move.vim b/src/nvim/testdir/test_move.vim index d774c93dbd..f666a904b0 100644 --- a/src/nvim/testdir/test_move.vim +++ b/src/nvim/testdir/test_move.vim @@ -35,6 +35,11 @@ func Test_move() call assert_fails('1,2move 1', 'E134') call assert_fails('2,3move 2', 'E134') + call assert_fails("move -100", 'E16:') + call assert_fails("move +100", 'E16:') + call assert_fails('move', 'E16:') %bwipeout! endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 5aef33cb09..8796af7a20 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -629,6 +629,25 @@ func Test_visualbell() set belloff=all endfunc +" Test for the 'write' option +func Test_write() + new + call setline(1, ['L1']) + set nowrite + call assert_fails('write Xfile', 'E142:') + set write + close! +endfunc + +" Test for 'buftype' option +func Test_buftype() + new + call setline(1, ['L1']) + set buftype=nowrite + call assert_fails('write', 'E382:') + close! +endfunc + " Test for setting option values using v:false and v:true func Test_opt_boolean() set number& diff --git a/src/nvim/testdir/test_search_stat.vim b/src/nvim/testdir/test_search_stat.vim index 11c6489ca2..335a51268d 100644 --- a/src/nvim/testdir/test_search_stat.vim +++ b/src/nvim/testdir/test_search_stat.vim @@ -8,6 +8,41 @@ func Test_search_stat() set shortmess-=S " Append 50 lines with text to search for, "foobar" appears 20 times call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10)) + call nvim_win_set_cursor(0, [1, 0]) + + " searchcount() returns an empty dictionary when previous pattern was not set + call assert_equal({}, searchcount(#{pattern: ''})) + " but setting @/ should also work (even 'n' nor 'N' was executed) + " recompute the count when the last position is different. + call assert_equal( + \ #{current: 1, exact_match: 1, total: 40, incomplete: 0, maxcount: 99}, + \ searchcount(#{pattern: 'foo'})) + call assert_equal( + \ #{current: 0, exact_match: 0, total: 10, incomplete: 0, maxcount: 99}, + \ searchcount(#{pattern: 'fooooobar'})) + call assert_equal( + \ #{current: 0, exact_match: 0, total: 10, incomplete: 0, maxcount: 99}, + \ searchcount(#{pattern: 'fooooobar', pos: [2, 1, 0]})) + call assert_equal( + \ #{current: 1, exact_match: 1, total: 10, incomplete: 0, maxcount: 99}, + \ searchcount(#{pattern: 'fooooobar', pos: [3, 1, 0]})) + " on last char of match + call assert_equal( + \ #{current: 1, exact_match: 1, total: 10, incomplete: 0, maxcount: 99}, + \ searchcount(#{pattern: 'fooooobar', pos: [3, 9, 0]})) + " on char after match + call assert_equal( + \ #{current: 1, exact_match: 0, total: 10, incomplete: 0, maxcount: 99}, + \ searchcount(#{pattern: 'fooooobar', pos: [3, 10, 0]})) + call assert_equal( + \ #{current: 1, exact_match: 0, total: 10, incomplete: 0, maxcount: 99}, + \ searchcount(#{pattern: 'fooooobar', pos: [4, 1, 0]})) + call assert_equal( + \ #{current: 1, exact_match: 0, total: 2, incomplete: 2, maxcount: 1}, + \ searchcount(#{pattern: 'fooooobar', pos: [4, 1, 0], maxcount: 1})) + call assert_equal( + \ #{current: 0, exact_match: 0, total: 2, incomplete: 2, maxcount: 1}, + \ searchcount(#{pattern: 'fooooobar', maxcount: 1})) " match at second line call cursor(1, 1) @@ -17,6 +52,9 @@ func Test_search_stat() let stat = '\[2/50\]' let pat = escape(@/, '()*?'). '\s\+' call assert_match(pat .. stat, g:a) + call assert_equal( + \ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 99}, + \ searchcount(#{recompute: 0})) " didn't get added to message history call assert_equal(messages_before, execute('messages')) @@ -25,6 +63,9 @@ func Test_search_stat() let g:a = execute(':unsilent :norm! n') let stat = '\[50/50\]' call assert_match(pat .. stat, g:a) + call assert_equal( + \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99}, + \ searchcount(#{recompute: 0})) " No search stat set shortmess+=S @@ -32,6 +73,14 @@ func Test_search_stat() let stat = '\[2/50\]' let g:a = execute(':unsilent :norm! n') call assert_notmatch(pat .. stat, g:a) + call writefile(getline(1, '$'), 'sample.txt') + " n does not update search stat + call assert_equal( + \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99}, + \ searchcount(#{recompute: 0})) + call assert_equal( + \ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 99}, + \ searchcount(#{recompute: v:true})) set shortmess-=S " Many matches @@ -41,10 +90,28 @@ func Test_search_stat() let g:a = execute(':unsilent :norm! n') let stat = '\[>99/>99\]' call assert_match(pat .. stat, g:a) + call assert_equal( + \ #{current: 100, exact_match: 0, total: 100, incomplete: 2, maxcount: 99}, + \ searchcount(#{recompute: 0})) + call assert_equal( + \ #{current: 272, exact_match: 1, total: 280, incomplete: 0, maxcount: 0}, + \ searchcount(#{recompute: v:true, maxcount: 0, timeout: 200})) + call assert_equal( + \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0}, + \ searchcount(#{recompute: 1, maxcount: 0, pos: [1, 1, 0], timeout: 200})) call cursor(line('$'), 1) let g:a = execute(':unsilent :norm! n') let stat = 'W \[1/>99\]' call assert_match(pat .. stat, g:a) + call assert_equal( + \ #{current: 1, exact_match: 1, total: 100, incomplete: 2, maxcount: 99}, + \ searchcount(#{recompute: 0})) + call assert_equal( + \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0}, + \ searchcount(#{recompute: 1, maxcount: 0, timeout: 200})) + call assert_equal( + \ #{current: 271, exact_match: 1, total: 280, incomplete: 0, maxcount: 0}, + \ searchcount(#{recompute: 1, maxcount: 0, pos: [line('$')-2, 1, 0], timeout: 200})) " Many matches call cursor(1, 1) @@ -180,12 +247,22 @@ func Test_search_stat() call assert_match('^\s\+' .. stat, g:b) unmap n + " Time out + %delete _ + call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 100000)) + call cursor(1, 1) + call assert_equal(1, searchcount(#{pattern: 'foo', maxcount: 0, timeout: 1}).incomplete) + " Clean up set shortmess+=S " close the window bwipe! endfunc +func Test_searchcount_fails() + call assert_fails('echo searchcount("boo!")', 'E715:') +endfunc + func Test_search_stat_foldopen() CheckScreendump @@ -252,9 +329,9 @@ func Test_searchcount_in_statusline() function TestSearchCount() abort let search_count = searchcount() if !empty(search_count) - return '[' . search_count.current . '/' . search_count.total . ']' + return '[' . search_count.current . '/' . search_count.total . ']' else - return '' + return '' endif endfunction set hlsearch diff --git a/src/nvim/testdir/test_sort.vim b/src/nvim/testdir/test_sort.vim index 7533eaf2e8..6d55889641 100644 --- a/src/nvim/testdir/test_sort.vim +++ b/src/nvim/testdir/test_sort.vim @@ -13,6 +13,37 @@ func Test_sort_strings() " numbers compared as strings call assert_equal([1, 2, 3], sort([3, 2, 1])) call assert_equal([13, 28, 3], sort([3, 28, 13])) + + call assert_equal(['A', 'O', 'P', 'a', 'o', 'p', 'Ä', 'Ô', 'ä', 'ô', 'Œ', 'œ'], + \ sort(['A', 'O', 'P', 'a', 'o', 'p', 'Ä', 'Ô', 'ä', 'ô', 'œ', 'Œ'])) + + call assert_equal(['A', 'a', 'o', 'O', 'p', 'P', 'Ä', 'Ô', 'ä', 'ô', 'Œ', 'œ'], + \ sort(['A', 'a', 'o', 'O', 'œ', 'Œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'i')) + + " This does not appear to work correctly on Mac. + if !has('mac') + if v:collate =~? '^\(en\|fr\)_ca.utf-\?8$' + " with Canadian English capitals come before lower case. + " 'Œ' is omitted because it can sort before or after 'œ' + call assert_equal(['A', 'a', 'Ä', 'ä', 'O', 'o', 'Ô', 'ô', 'œ', 'P', 'p'], + \ sort(['A', 'a', 'o', 'O', 'œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'l')) + elseif v:collate =~? '^\(en\|es\|de\|fr\|it\|nl\).*\.utf-\?8$' + " With the following locales, the accentuated letters are ordered + " similarly to the non-accentuated letters... + call assert_equal(['a', 'A', 'ä', 'Ä', 'o', 'O', 'ô', 'Ô', 'œ', 'Œ', 'p', 'P'], + \ sort(['A', 'a', 'o', 'O', 'œ', 'Œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'l')) + elseif v:collate =~? '^sv.*utf-\?8$' + " ... whereas with a Swedish locale, the accentuated letters are ordered + " after Z. + call assert_equal(['a', 'A', 'o', 'O', 'p', 'P', 'ä', 'Ä', 'œ', 'œ', 'ô', 'Ô'], + \ sort(['A', 'a', 'o', 'O', 'œ', 'œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'l')) + endif + endif +endfunc + +func Test_sort_null_string() + " null strings are sorted as empty strings. + call assert_equal(['', 'a', 'b'], sort(['b', v:_null_string, 'a'])) endfunc func Test_sort_numeric() @@ -1150,7 +1181,7 @@ func Test_sort_cmd() \ 'input' : [ \ '1.234', \ '0.88', - \ '123.456', + \ ' + 123.456', \ '1.15e-6', \ '-1.1e3', \ '-1.01e3', @@ -1165,7 +1196,7 @@ func Test_sort_cmd() \ '1.15e-6', \ '0.88', \ '1.234', - \ '123.456' + \ ' + 123.456' \ ] \ }, \ { @@ -1197,8 +1228,133 @@ func Test_sort_cmd() \ 'cc', \ ] \ }, + \ { + \ 'name' : 'sort one line buffer', + \ 'cmd' : 'sort', + \ 'input' : [ + \ 'single line' + \ ], + \ 'expected' : [ + \ 'single line' + \ ] + \ }, + \ { + \ 'name' : 'sort ignoring case', + \ 'cmd' : '%sort i', + \ 'input' : [ + \ 'BB', + \ 'Cc', + \ 'aa' + \ ], + \ 'expected' : [ + \ 'aa', + \ 'BB', + \ 'Cc' + \ ] + \ }, \ ] + " This does not appear to work correctly on Mac. + if !has('mac') + if v:collate =~? '^\(en\|fr\)_ca.utf-\?8$' + " en_CA.utf-8 sorts capitals before lower case + " 'Œ' is omitted because it can sort before or after 'œ' + let tests += [ + \ { + \ 'name' : 'sort with locale ' .. v:collate, + \ 'cmd' : '%sort l', + \ 'input' : [ + \ 'A', + \ 'E', + \ 'O', + \ 'À', + \ 'È', + \ 'É', + \ 'Ô', + \ 'Z', + \ 'a', + \ 'e', + \ 'o', + \ 'à', + \ 'è', + \ 'é', + \ 'ô', + \ 'œ', + \ 'z' + \ ], + \ 'expected' : [ + \ 'A', + \ 'a', + \ 'À', + \ 'à', + \ 'E', + \ 'e', + \ 'É', + \ 'é', + \ 'È', + \ 'è', + \ 'O', + \ 'o', + \ 'Ô', + \ 'ô', + \ 'œ', + \ 'Z', + \ 'z' + \ ] + \ }, + \ ] + elseif v:collate =~? '^\(en\|es\|de\|fr\|it\|nl\).*\.utf-\?8$' + " With these locales, the accentuated letters are ordered + " similarly to the non-accentuated letters. + let tests += [ + \ { + \ 'name' : 'sort with locale ' .. v:collate, + \ 'cmd' : '%sort l', + \ 'input' : [ + \ 'A', + \ 'E', + \ 'O', + \ 'À', + \ 'È', + \ 'É', + \ 'Ô', + \ 'Œ', + \ 'Z', + \ 'a', + \ 'e', + \ 'o', + \ 'à', + \ 'è', + \ 'é', + \ 'ô', + \ 'œ', + \ 'z' + \ ], + \ 'expected' : [ + \ 'a', + \ 'A', + \ 'à', + \ 'À', + \ 'e', + \ 'E', + \ 'é', + \ 'É', + \ 'è', + \ 'È', + \ 'o', + \ 'O', + \ 'ô', + \ 'Ô', + \ 'œ', + \ 'Œ', + \ 'z', + \ 'Z' + \ ] + \ }, + \ ] + endif + endif + for t in tests enew! call append(0, t.input) @@ -1217,7 +1373,11 @@ func Test_sort_cmd() endif endfor - call assert_fails('sort no', 'E474') + " Needs atleast two lines for this test + call setline(1, ['line1', 'line2']) + call assert_fails('sort no', 'E474:') + call assert_fails('sort c', 'E475:') + call assert_fails('sort #pat%', 'E682:') enew! endfunc @@ -1319,4 +1479,46 @@ func Test_sort_cmd_report() " the output comes from the :g command, not from the :sort call assert_match("6 fewer lines", res) enew! - endfunc +endfunc + +" Test for a :sort command followed by another command +func Test_sort_followed_by_cmd() + new + let var = '' + call setline(1, ['cc', 'aa', 'bb']) + %sort | let var = "sortcmdtest" + call assert_equal(var, "sortcmdtest") + call assert_equal(['aa', 'bb', 'cc'], getline(1, '$')) + " Test for :sort followed by a comment + call setline(1, ['3b', '1c', '2a']) + %sort /\d\+/ " sort alphabetically + call assert_equal(['2a', '3b', '1c'], getline(1, '$')) + close! +endfunc + +" Test for :sort using last search pattern +func Test_sort_last_search_pat() + new + let @/ = '\d\+' + call setline(1, ['3b', '1c', '2a']) + sort // + call assert_equal(['2a', '3b', '1c'], getline(1, '$')) + close! +endfunc + +" Test for retaining marks across a :sort +func Test_sort_with_marks() + new + call setline(1, ['cc', 'aa', 'bb']) + call setpos("'c", [0, 1, 0, 0]) + call setpos("'a", [0, 2, 0, 0]) + call setpos("'b", [0, 3, 0, 0]) + %sort + call assert_equal(['aa', 'bb', 'cc'], getline(1, '$')) + call assert_equal(2, line("'a")) + call assert_equal(3, line("'b")) + call assert_equal(1, line("'c")) + close! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index f5b6446108..a3e4dcdd25 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -241,6 +241,26 @@ func Test_statusline() call assert_match('^vimLineComment\s*$', s:get_statusline()) syntax off + "%{%expr%}: evaluates enxpressions present in result of expr + func! Inner_eval() + return '%n some other text' + endfunc + func! Outer_eval() + return 'some text %{%Inner_eval()%}' + endfunc + set statusline=%{%Outer_eval()%} + call assert_match('^some text ' . bufnr() . ' some other text\s*$', s:get_statusline()) + delfunc Inner_eval + delfunc Outer_eval + + "%{%expr%}: Doesn't get stuck in recursion + func! Recurse_eval() + return '%{%Recurse_eval()%}' + endfunc + set statusline=%{%Recurse_eval()%} + call assert_match('^%{%Recurse_eval()%}\s*$', s:get_statusline()) + delfunc Recurse_eval + "%(: Start of item group. set statusline=ab%(cd%q%)de call assert_match('^abde\s*$', s:get_statusline()) diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index 32167a45ba..e7f9bb76f2 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -426,6 +426,8 @@ func Test_substitute_errors() call assert_fails('s/FOO/bar/', 'E486:') call assert_fails('s/foo/bar/@', 'E488:') call assert_fails('s/\(/bar/', 'E476:') + call assert_fails('s afooabara', 'E146:') + call assert_fails('s\\a', 'E10:') setl nomodifiable call assert_fails('s/foo/bar/', 'E21:') diff --git a/src/nvim/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim index ee43540fdd..7f5b80e8d3 100644 --- a/src/nvim/testdir/test_winbuf_close.vim +++ b/src/nvim/testdir/test_winbuf_close.vim @@ -160,7 +160,7 @@ func Test_winfixwidth_on_close() endfunction " Test that 'winfixheight' will be respected even there is non-leaf frame -fun! Test_winfixheight_non_leaf_frame() +func Test_winfixheight_non_leaf_frame() vsplit botright 11new let l:wid = win_getid() @@ -173,7 +173,7 @@ fun! Test_winfixheight_non_leaf_frame() endf " Test that 'winfixwidth' will be respected even there is non-leaf frame -fun! Test_winfixwidth_non_leaf_frame() +func Test_winfixwidth_non_leaf_frame() split topleft 11vnew let l:wid = win_getid() @@ -184,3 +184,13 @@ fun! Test_winfixwidth_non_leaf_frame() call assert_equal(11, winwidth(l:wid)) %bwipe! endf + +func Test_tabwin_close() + enew + let l:wid = win_getid() + tabedit + call win_execute(l:wid, 'close') + " Should not crash. + call assert_true(v:true) + %bwipe! +endfunc diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index c62c01d5f3..c7710ff198 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -164,6 +164,69 @@ func Test_writefile_autowrite_nowrite() set noautowrite endfunc +" Test for ':w !<cmd>' to pipe lines from the current buffer to an external +" command. +func Test_write_pipe_to_cmd() + if !has('unix') + return + endif + new + call setline(1, ['L1', 'L2', 'L3', 'L4']) + 2,3w !cat > Xfile + call assert_equal(['L2', 'L3'], readfile('Xfile')) + close! + call delete('Xfile') +endfunc + +" Test for :saveas +func Test_saveas() + call assert_fails('saveas', 'E471:') + call writefile(['L1'], 'Xfile') + new Xfile + new + call setline(1, ['L1']) + call assert_fails('saveas Xfile', 'E139:') + close! + enew | only + call delete('Xfile') +endfunc + +func Test_write_errors() + " Test for writing partial buffer + call writefile(['L1', 'L2', 'L3'], 'Xfile') + new Xfile + call assert_fails('1,2write', 'E140:') + close! + + " Try to overwrite a directory + if has('unix') + call mkdir('Xdir1') + call assert_fails('write Xdir1', 'E17:') + call delete('Xdir1', 'd') + endif + + " Test for :wall for a buffer with no name + enew | only + call setline(1, ['L1']) + call assert_fails('wall', 'E141:') + enew! + + " Test for writing a 'readonly' file + new Xfile + set readonly + call assert_fails('write', 'E45:') + close + + " Test for writing to a read-only file + new Xfile + call setfperm('Xfile', 'r--r--r--') + call assert_fails('write', 'E505:') + call setfperm('Xfile', 'rw-rw-rw-') + close + + call delete('Xfile') +endfunc + func Test_writefile_sync_dev_stdout() if !has('unix') return diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index ed40a64c66..fd83681aed 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -2009,9 +2009,9 @@ static void augment_terminfo(TUIData *data, const char *term, } data->unibi_ext.save_title = (int)unibi_add_ext_str( - ut, "ext.save_title", "\x1b[22;0;0t"); + ut, "ext.save_title", "\x1b[22;0t"); data->unibi_ext.restore_title = (int)unibi_add_ext_str( - ut, "ext.restore_title", "\x1b[23;0;0t"); + ut, "ext.restore_title", "\x1b[23;0t"); /// Terminals usually ignore unrecognized private modes, and there is no /// known ambiguity with these. So we just set them unconditionally. diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index c1e4a40ef2..1ec5189795 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -165,22 +165,13 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, } #endif - // TODO(bfredl): this is pretty ad-hoc, add a proper z-order/priority - // scheme. For now: - // - msg_grid is always on top. - // - pum_grid is on top of all windows but not msg_grid. Except for when - // wildoptions=pum, and completing the cmdline with scrolled messages, - // then the pum has to be drawn over the scrolled messages. size_t insert_at = kv_size(layers); - bool cmd_completion = (grid == &pum_grid && (State & CMDLINE) - && (wop_flags & WOP_PUM)); - if (kv_A(layers, insert_at-1) == &msg_grid && !cmd_completion) { - insert_at--; - } - if (kv_A(layers, insert_at-1) == &pum_grid && (grid != &msg_grid)) { + while (insert_at > 0 && kv_A(layers, insert_at-1)->zindex > grid->zindex) { insert_at--; } + if (curwin && kv_A(layers, insert_at-1) == &curwin->w_grid_alloc + && kv_A(layers, insert_at-1)->zindex == grid->zindex && !on_top) { insert_at--; } @@ -279,12 +270,11 @@ static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle, // should configure all grids before entering win_update() if (curgrid != &default_grid) { size_t new_index = kv_size(layers)-1; - if (kv_A(layers, new_index) == &msg_grid) { - new_index--; - } - if (kv_A(layers, new_index) == &pum_grid) { + + while (new_index > 1 && kv_A(layers, new_index)->zindex > curgrid->zindex) { new_index--; } + if (curgrid->comp_index < new_index) { ui_comp_raise_grid(curgrid, new_index); } diff --git a/src/nvim/window.c b/src/nvim/window.c index c070f0a32e..936bfa8c5b 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -763,10 +763,13 @@ void ui_ext_win_position(win_T *wp) } api_clear_error(&dummy); } + + wp->w_grid_alloc.zindex = wp->w_float_config.zindex; if (ui_has(kUIMultigrid)) { String anchor = cstr_to_string(float_anchor_str[c.anchor]); ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor, - grid->handle, row, col, c.focusable); + grid->handle, row, col, c.focusable, + wp->w_grid_alloc.zindex); } else { // TODO(bfredl): ideally, compositor should work like any multigrid UI // and use standard win_pos events. @@ -2286,7 +2289,7 @@ int win_close(win_T *win, bool free_buf) return FAIL; // window is already being closed } if (win == aucmd_win) { - EMSG(_("E813: Cannot close autocmd window")); + EMSG(_(e_autocmd_close)); return FAIL; } if ((firstwin == aucmd_win || lastwin == aucmd_win) && one_window()) { @@ -3466,6 +3469,9 @@ int win_alloc_first(void) first_tabpage = alloc_tabpage(); first_tabpage->tp_topframe = topframe; curtab = first_tabpage; + curtab->tp_firstwin = firstwin; + curtab->tp_lastwin = lastwin; + curtab->tp_curwin = curwin; return OK; } @@ -3634,6 +3640,8 @@ int win_new_tabpage(int after, char_u *filename) newtp->tp_next = tp->tp_next; tp->tp_next = newtp; } + newtp->tp_firstwin = newtp->tp_lastwin = newtp->tp_curwin = curwin; + win_init_size(); firstwin->w_winrow = tabline_height(); win_comp_scroll(curwin); @@ -6336,6 +6344,13 @@ static win_T *get_snapshot_focus(int idx) int switch_win(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage_T *tp, int no_display) { block_autocmds(); + return switch_win_noblock(save_curwin, save_curtab, win, tp, no_display); +} + +// As switch_win() but without blocking autocommands. +int switch_win_noblock(win_T **save_curwin, tabpage_T **save_curtab, + win_T *win, tabpage_T *tp, int no_display) +{ *save_curwin = curwin; if (tp != NULL) { *save_curtab = curtab; @@ -6361,6 +6376,14 @@ int switch_win(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage // triggered. void restore_win(win_T *save_curwin, tabpage_T *save_curtab, bool no_display) { + restore_win_noblock(save_curwin, save_curtab, no_display); + unblock_autocmds(); +} + +// As restore_win() but without unblocking autocommands. +void restore_win_noblock(win_T *save_curwin, tabpage_T *save_curtab, + bool no_display) +{ if (save_curtab != NULL && valid_tabpage(save_curtab)) { if (no_display) { curtab->tp_firstwin = firstwin; @@ -6375,7 +6398,6 @@ void restore_win(win_T *save_curwin, tabpage_T *save_curtab, bool no_display) curwin = save_curwin; curbuf = curwin->w_buffer; } - unblock_autocmds(); } /// Make "buf" the current buffer. @@ -6808,10 +6830,19 @@ void win_id2tabwin(typval_T *const argvars, typval_T *const rettv) win_T * win_id2wp(typval_T *argvars) { + return win_id2wp_tp(argvars, NULL); +} + +// Return the window and tab pointer of window "id". +win_T * win_id2wp_tp(typval_T *argvars, tabpage_T **tpp) +{ int id = tv_get_number(&argvars[0]); FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->handle == id) { + if (tpp != NULL) { + *tpp = tp; + } return wp; } } diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index 058706718a..21e3094f8e 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -158,3 +158,98 @@ describe('API: highlight',function() assert_alive() end) end) + +describe("API: set highlight", function() + local highlight_color = { + fg = tonumber('0xff0000'), + bg = tonumber('0x0032aa'), + ctermfg = 8, + ctermbg = 15, + } + local highlight1 = { + background = highlight_color.bg, + foreground = highlight_color.fg, + bold = true, + italic = true, + } + local highlight2_config = { + ctermbg = highlight_color.ctermbg, + ctermfg = highlight_color.ctermfg, + underline = true, + reverse = true, + } + local highlight2_result = { + background = highlight_color.ctermbg, + foreground = highlight_color.ctermfg, + underline = true, + reverse = true, + } + local highlight3_config = { + background = highlight_color.bg, + foreground = highlight_color.fg, + ctermbg = highlight_color.ctermbg, + ctermfg = highlight_color.ctermfg, + bold = true, + italic = true, + reverse = true, + undercurl = true, + underline = true, + cterm = { + italic = true, + reverse = true, + undercurl = true, + } + } + local highlight3_result_gui = { + background = highlight_color.bg, + foreground = highlight_color.fg, + bold = true, + italic = true, + reverse = true, + undercurl = true, + underline = true, + } + local highlight3_result_cterm = { + background = highlight_color.ctermbg, + foreground = highlight_color.ctermfg, + italic = true, + reverse = true, + undercurl = true, + } + + local function get_ns() + local ns = meths.create_namespace('Test_set_hl') + meths._set_hl_ns(ns) + return ns + end + + before_each(clear) + + it ("can set gui highlight", function() + local ns = get_ns() + meths.set_hl(ns, 'Test_hl', highlight1) + eq(highlight1, meths.get_hl_by_name('Test_hl', true)) + end) + + it ("can set cterm highlight", function() + local ns = get_ns() + meths.set_hl(ns, 'Test_hl', highlight2_config) + eq(highlight2_result, meths.get_hl_by_name('Test_hl', false)) + end) + + it ("cterm attr defaults to gui attr", function() + local ns = get_ns() + meths.set_hl(ns, 'Test_hl', highlight1) + eq({ + bold = true, + italic = true, + }, meths.get_hl_by_name('Test_hl', false)) + end) + + it ("can overwrite attr for cterm", function() + local ns = get_ns() + meths.set_hl(ns, 'Test_hl', highlight3_config) + eq(highlight3_result_gui, meths.get_hl_by_name('Test_hl', true)) + eq(highlight3_result_cterm, meths.get_hl_by_name('Test_hl', false)) + end) +end) diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index d403fbc878..4f29811095 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -73,6 +73,7 @@ describe('NULL', function() null_expr_test('does not crash col()', 'col(L)', 0, 0) null_expr_test('does not crash virtcol()', 'virtcol(L)', 0, 0) null_expr_test('does not crash line()', 'line(L)', 0, 0) + null_expr_test('does not crash line() with window id', 'line(L, 1000)', 0, 0) null_expr_test('does not crash count()', 'count(L, 1)', 0, 0) null_expr_test('does not crash cursor()', 'cursor(L)', 'E474: Invalid argument', -1) null_expr_test('does not crash map()', 'map(L, "v:val")', 0, {}) diff --git a/test/functional/legacy/packadd_spec.lua b/test/functional/legacy/packadd_spec.lua index 609f825177..486a1fe471 100644 --- a/test/functional/legacy/packadd_spec.lua +++ b/test/functional/legacy/packadd_spec.lua @@ -267,6 +267,8 @@ describe('packadd', function() call assert_match('look-here', tags1[0]) let tags2 = readfile(docdir2 . '/tags') call assert_match('look-away', tags2[0]) + + call assert_fails('helptags abcxyz', 'E150:') endfunc func Test_colorscheme() diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index 5da8452a51..5bfab1d52d 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -936,6 +936,51 @@ describe('lua: nvim_buf_attach on_bytes', function() } end) + it("block visual paste", function() + local check_events = setup_eventcheck(verify, {"AAA", + "BBB", + "CCC", + "DDD", + "EEE", + "FFF"}) + funcs.setreg("a", "___") + feed([[gg0l<c-v>3jl"ap]]) + + check_events { + { "test1", "bytes", 1, 3, 0, 1, 1, 0, 2, 2, 0, 0, 0 }; + { "test1", "bytes", 1, 3, 1, 1, 3, 0, 2, 2, 0, 0, 0 }; + { "test1", "bytes", 1, 3, 2, 1, 5, 0, 2, 2, 0, 0, 0 }; + { "test1", "bytes", 1, 3, 3, 1, 7, 0, 2, 2, 0, 0, 0 }; + { "test1", "bytes", 1, 5, 0, 1, 1, 0, 0, 0, 0, 3, 3 }; + { "test1", "bytes", 1, 6, 1, 1, 6, 0, 0, 0, 0, 3, 3 }; + { "test1", "bytes", 1, 7, 2, 1, 11, 0, 0, 0, 0, 3, 3 }; + { "test1", "bytes", 1, 8, 3, 1, 16, 0, 0, 0, 0, 3, 3 }; + } + end) + + it("nvim_buf_set_lines", function() + local check_events = setup_eventcheck(verify, {"AAA", "BBB"}) + + -- delete + meths.buf_set_lines(0, 0, 1, true, {}) + + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 1, 0, 4, 0, 0, 0 }; + } + + -- add + meths.buf_set_lines(0, 0, 0, true, {'asdf'}) + check_events { + { "test1", "bytes", 1, 4, 0, 0, 0, 0, 0, 0, 1, 0, 5 }; + } + + -- replace + meths.buf_set_lines(0, 0, 1, true, {'asdf', 'fdsa'}) + check_events { + { "test1", "bytes", 1, 5, 0, 0, 0, 1, 0, 5, 2, 0, 10 }; + } + end) + teardown(function() os.remove "Xtest-reload" os.remove "Xtest-undofile" diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 9bf00b594b..6566da4807 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -945,12 +945,20 @@ describe('lua stdlib', function() exec_lua [[ vim.api.nvim_set_var("testing", "hi") vim.api.nvim_set_var("other", 123) + vim.api.nvim_set_var("floaty", 5120.1) + vim.api.nvim_set_var("nullvar", vim.NIL) vim.api.nvim_set_var("to_delete", {hello="world"}) ]] eq('hi', funcs.luaeval "vim.g.testing") eq(123, funcs.luaeval "vim.g.other") + eq(5120.1, funcs.luaeval "vim.g.floaty") eq(NIL, funcs.luaeval "vim.g.nonexistant") + eq(NIL, funcs.luaeval "vim.g.nullvar") + -- lost over RPC, so test locally: + eq({false, true}, exec_lua [[ + return {vim.g.nonexistant == vim.NIL, vim.g.nullvar == vim.NIL} + ]]) eq({hello="world"}, funcs.luaeval "vim.g.to_delete") exec_lua [[ @@ -963,12 +971,20 @@ describe('lua stdlib', function() exec_lua [[ vim.api.nvim_buf_set_var(0, "testing", "hi") vim.api.nvim_buf_set_var(0, "other", 123) + vim.api.nvim_buf_set_var(0, "floaty", 5120.1) + vim.api.nvim_buf_set_var(0, "nullvar", vim.NIL) vim.api.nvim_buf_set_var(0, "to_delete", {hello="world"}) ]] eq('hi', funcs.luaeval "vim.b.testing") eq(123, funcs.luaeval "vim.b.other") + eq(5120.1, funcs.luaeval "vim.b.floaty") eq(NIL, funcs.luaeval "vim.b.nonexistant") + eq(NIL, funcs.luaeval "vim.b.nullvar") + -- lost over RPC, so test locally: + eq({false, true}, exec_lua [[ + return {vim.b.nonexistant == vim.NIL, vim.b.nullvar == vim.NIL} + ]]) eq({hello="world"}, funcs.luaeval "vim.b.to_delete") exec_lua [[ diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index c136ae652f..663271deab 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -9,6 +9,7 @@ local eq = helpers.eq local pcall_err = helpers.pcall_err local pesc = helpers.pesc local insert = helpers.insert +local funcs = helpers.funcs local retry = helpers.retry local NIL = helpers.NIL local read_file = require('test.helpers').read_file @@ -1820,36 +1821,20 @@ describe('LSP', function() end) describe('lsp.util.jump_to_location', function() - local default_target_bufnr - local default_target_uri = 'file://fake/uri' - - local create_buf = function(uri, lines) - for i, line in ipairs(lines) do - lines[i] = '"' .. line .. '"' - end - lines = table.concat(lines, ", ") - - -- Let's set "hidden" to true in order to avoid errors when switching - -- between buffers in test. - local code = string.format([[ - vim.api.nvim_set_option('hidden', true) + local target_bufnr - local bufnr = vim.uri_to_bufnr("%s") - local lines = {%s} + before_each(function() + target_bufnr = exec_lua [[ + local bufnr = vim.uri_to_bufnr("file://fake/uri") + local lines = {"1st line of text", "å å ɧ 汉语 ↥ 🤦 🦄"} vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) return bufnr - ]], uri, lines) - - return exec_lua(code) - end - - before_each(function() - default_target_bufnr = create_buf(default_target_uri, {'1st line of text', 'å å ɧ 汉语 ↥ 🤦 🦄'}) + ]] end) - local location = function(uri, start_line, start_char, end_line, end_char) + local location = function(start_line, start_char, end_line, end_char) return { - uri = uri, + uri = "file://fake/uri", range = { start = { line = start_line, character = start_char }, ["end"] = { line = end_line, character = end_char }, @@ -1857,9 +1842,9 @@ describe('LSP', function() } end - local jump = function(bufnr, msg) + local jump = function(msg) eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg)) - eq(bufnr, exec_lua[[return vim.fn.bufnr('%')]]) + eq(target_bufnr, exec_lua[[return vim.fn.bufnr('%')]]) return { line = exec_lua[[return vim.fn.line('.')]], col = exec_lua[[return vim.fn.col('.')]], @@ -1867,13 +1852,13 @@ describe('LSP', function() end it('jumps to a Location', function() - local pos = jump(default_target_bufnr, location(default_target_uri, 0, 9, 0, 9)) + local pos = jump(location(0, 9, 0, 9)) eq(1, pos.line) eq(10, pos.col) end) it('jumps to a LocationLink', function() - local pos = jump(default_target_bufnr, { + local pos = jump({ targetUri = "file://fake/uri", targetSelectionRange = { start = { line = 0, character = 4 }, @@ -1889,104 +1874,22 @@ describe('LSP', function() end) it('jumps to the correct multibyte column', function() - local pos = jump(default_target_bufnr, location(default_target_uri, 1, 2, 1, 2)) + local pos = jump(location(1, 2, 1, 2)) eq(2, pos.line) eq(4, pos.col) eq('å', exec_lua[[return vim.fn.expand('<cword>')]]) end) it('adds current position to jumplist before jumping', function() - exec_lua([[ - vim.api.nvim_win_set_buf(0, ...) - vim.api.nvim_win_set_cursor(0, {2, 0}) - ]], default_target_bufnr) - jump(default_target_bufnr, location(default_target_uri, 0, 9, 0, 9)) - - local mark = exec_lua([[return vim.inspect(vim.api.nvim_buf_get_mark(..., "'"))]], default_target_bufnr) - eq('{ 2, 0 }', mark) - end) - - it('should not push item to tagstack if destination is the same as source', function() - -- Set cursor at the 2nd line, 1st character. This is the source position - -- for the test, and will also be the destination one, making the cursor - -- "motionless", thus not triggering a push to the tagstack. - exec_lua(string.format([[ - vim.api.nvim_win_set_buf(0, %d) - vim.api.nvim_win_set_cursor(0, {2, 0}) - ]], default_target_bufnr)) - - -- Jump to 'f' in 'foobar', at the 2nd line. - jump(default_target_bufnr, location(default_target_uri, 1, 0, 1, 0)) + funcs.nvim_win_set_buf(0, target_bufnr) + local mark = funcs.nvim_buf_get_mark(target_bufnr, "'") + eq({ 1, 0 }, mark) - local stack = exec_lua[[return vim.fn.gettagstack()]] - eq(0, stack.length) - end) - - it('should not push the same item from same buffer twice to tagstack', function() - -- Set cursor at the 2nd line, 5th character. - exec_lua(string.format([[ - vim.api.nvim_win_set_buf(0, %d) - vim.api.nvim_win_set_cursor(0, {2, 4}) - ]], default_target_bufnr)) - - local stack - - -- Jump to 1st line, 1st column. - jump(default_target_bufnr, location(default_target_uri, 0, 0, 0, 0)) - - stack = exec_lua[[return vim.fn.gettagstack()]] - eq({default_target_bufnr, 2, 5, 0}, stack.items[1].from) - - -- Go back to 5th character at 2nd line, which is currently at the top of - -- the tagstack. - exec_lua(string.format([[ - vim.api.nvim_win_set_cursor(0, {2, 4}) - ]], default_target_bufnr)) - - -- Jump again to 1st line, 1st column. Since we're jumping from the same - -- position we have just jumped from, this jump shouldn't be pushed to - -- the tagstack. - jump(default_target_bufnr, location(default_target_uri, 0, 0, 0, 0)) + funcs.nvim_win_set_cursor(0, {2, 3}) + jump(location(0, 9, 0, 9)) - stack = exec_lua[[return vim.fn.gettagstack()]] - eq({default_target_bufnr, 2, 5, 0}, stack.items[1].from) - eq(1, stack.length) - end) - - it('should not push the same item from another buffer twice to tagstack', function() - local target_uri = 'file://foo/bar' - local target_bufnr = create_buf(target_uri, {'this is a line', 'foobar'}) - - -- Set cursor at the 1st line, 3rd character of the default test buffer. - exec_lua(string.format([[ - vim.api.nvim_win_set_buf(0, %d) - vim.api.nvim_win_set_cursor(0, {1, 2}) - ]], default_target_bufnr)) - - local stack - - -- Jump to 1st line, 1st column of a different buffer from the source - -- position. - jump(target_bufnr, location(target_uri, 0, 0, 0, 0)) - - stack = exec_lua[[return vim.fn.gettagstack()]] - eq({default_target_bufnr, 1, 3, 0}, stack.items[1].from) - - -- Go back to 3rd character at 1st line of the default test buffer, which - -- is currently at the top of the tagstack. - exec_lua(string.format([[ - vim.api.nvim_win_set_buf(0, %d) - vim.api.nvim_win_set_cursor(0, {1, 2}) - ]], default_target_bufnr)) - - -- Jump again to 1st line, 1st column of the different buffer. Since - -- we're jumping from the same position we have just jumped from, this - -- jump shouldn't be pushed to the tagstack. - jump(target_bufnr, location(target_uri, 0, 0, 0, 0)) - - stack = exec_lua[[return vim.fn.gettagstack()]] - eq({default_target_bufnr, 1, 3, 0}, stack.items[1].from) - eq(1, stack.length) + mark = funcs.nvim_buf_get_mark(target_bufnr, "'") + eq({ 2, 3 }, mark) end) end) diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index f267f9fb5d..d2f9148e8f 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -235,6 +235,100 @@ void ui_refresh(void) }, res) end) + it('can match special regex characters like \\ * + ( with `vim-match?`', function() + if pending_c_parser(pending) then return end + + insert('char* astring = "\\n"; (1 + 1) * 2 != 2;') + + local res = exec_lua([[ + cquery = vim.treesitter.parse_query("c", '((_) @plus (vim-match? @plus "^\\\\+$"))'.. + '((_) @times (vim-match? @times "^\\\\*$"))'.. + '((_) @paren (vim-match? @paren "^\\\\($"))'.. + '((_) @escape (vim-match? @escape "^\\\\\\\\n$"))'.. + '((_) @string (vim-match? @string "^\\"\\\\\\\\n\\"$"))') + parser = vim.treesitter.get_parser(0, "c") + tree = parser:parse()[1] + res = {} + for pattern, match in cquery:iter_matches(tree:root(), 0) do + -- can't transmit node over RPC. just check the name and range + local mrepr = {} + for cid,node in pairs(match) do + table.insert(mrepr, {cquery.captures[cid], node:type(), node:range()}) + end + table.insert(res, {pattern, mrepr}) + end + return res + ]]) + + eq({ + { 2, { { "times", '*', 0, 4, 0, 5 } } }, + { 5, { { "string", 'string_literal', 0, 16, 0, 20 } } }, + { 4, { { "escape", 'escape_sequence', 0, 17, 0, 19 } } }, + { 3, { { "paren", '(', 0, 22, 0, 23 } } }, + { 1, { { "plus", '+', 0, 25, 0, 26 } } }, + { 2, { { "times", '*', 0, 30, 0, 31 } } }, + }, res) + end) + + it('supports builtin query predicate any-of?', function() + if pending_c_parser(pending) then return end + + insert([[ + #include <stdio.h> + + int main(void) { + int i; + for(i=1; i<=100; i++) { + if(((i%3)||(i%5))== 0) + printf("number= %d FizzBuzz\n", i); + else if((i%3)==0) + printf("number= %d Fizz\n", i); + else if((i%5)==0) + printf("number= %d Buzz\n", i); + else + printf("number= %d\n",i); + } + return 0; + } + ]]) + exec_lua([[ + function get_query_result(query_text) + cquery = vim.treesitter.parse_query("c", query_text) + parser = vim.treesitter.get_parser(0, "c") + tree = parser:parse()[1] + res = {} + for cid, node in cquery:iter_captures(tree:root(), 0) do + -- can't transmit node over RPC. just check the name, range, and text + local text = vim.treesitter.get_node_text(node, 0) + local range = {node:range()} + table.insert(res, {cquery.captures[cid], node:type(), range, text}) + end + return res + end + ]]) + + local res0 = exec_lua([[return get_query_result(...)]], + [[((primitive_type) @c-keyword (#any-of? @c-keyword "int" "float"))]]) + eq({ + { "c-keyword", "primitive_type", { 2, 2, 2, 5 }, "int" }, + { "c-keyword", "primitive_type", { 3, 4, 3, 7 }, "int" }, + }, res0) + + local res1 = exec_lua([[return get_query_result(...)]], + [[ + ((string_literal) @fizzbuzz-strings (#any-of? @fizzbuzz-strings + "\"number= %d FizzBuzz\\n\"" + "\"number= %d Fizz\\n\"" + "\"number= %d Buzz\\n\"" + )) + ]]) + eq({ + { "fizzbuzz-strings", "string_literal", { 6, 15, 6, 38 }, "\"number= %d FizzBuzz\\n\""}, + { "fizzbuzz-strings", "string_literal", { 8, 15, 8, 34 }, "\"number= %d Fizz\\n\""}, + { "fizzbuzz-strings", "string_literal", { 10, 15, 10, 34 }, "\"number= %d Buzz\\n\""}, + }, res1) + end) + it('allow loading query with escaped quotes and capture them with `lua-match?` and `vim-match?`', function() if pending_c_parser(pending) then return end @@ -308,7 +402,7 @@ void ui_refresh(void) return list ]] - eq({ 'contains?', 'eq?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list) + eq({ 'any-of?', 'contains?', 'eq?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list) end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index cd3bea6595..f3b840da21 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -62,6 +62,27 @@ describe('float window', function() eq(1000, funcs.win_getid()) end) + it('win_execute() should work' , function() + local buf = meths.create_buf(false, false) + meths.buf_set_lines(buf, 0, -1, true, {'the floatwin', 'abc', 'def'}) + local win = meths.open_win(buf, false, {relative='win', width=16, height=1, row=0, col=10}) + local line = funcs.win_execute(win, 'echo getline(1)') + eq('\nthe floatwin', line) + eq('\n1', funcs.win_execute(win, 'echo line(".",'..win.id..')')) + eq('\n3', funcs.win_execute(win, 'echo line("$",'..win.id..')')) + eq('\n0', funcs.win_execute(win, 'echo line("$", 123456)')) + funcs.win_execute(win, 'bwipe!') + end) + + it('win_execute() call commands that not allowed' , function() + local buf = meths.create_buf(false, false) + meths.buf_set_lines(buf, 0, -1, true, {'the floatwin'}) + local win = meths.open_win(buf, true, {relative='win', width=16, height=1, row=0, col=10}) + eq(pcall_err(funcs.win_execute, win, 'close'), 'Vim(close):E37: No write since last change (add ! to override)') + eq(pcall_err(funcs.win_execute, win, 'bdelete'), 'Vim(bdelete):E89: No write since last change for buffer 2 (add ! to override)') + funcs.win_execute(win, 'bwipe!') + end) + it('closed immediately by autocmd #11383', function() eq('Error executing lua: [string "<nvim>"]:0: Window was closed immediately', pcall_err(exec_lua, [[ @@ -1059,8 +1080,8 @@ describe('float window', function() {1: abb }| {13: acc }| ]], float_pos={ - [5] = { { id = 1002 }, "NW", 1, 0, 5, true }, - [6] = { { id = -1 }, "NW", 5, 4, 0, false } + [5] = { { id = 1002 }, "NW", 1, 0, 5, true, 50 }, + [6] = { { id = -1 }, "NW", 5, 4, 0, false, 100 } }, win_viewport={ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 2, curcol = 3}; @@ -2737,8 +2758,8 @@ describe('float window', function() {1: word }| {1: longtext }| ]], float_pos={ - [4] = {{ id = 1001 }, "NW", 1, 2, 5, true}, - [5] = {{ id = -1 }, "NW", 4, 1, 1, false} + [4] = {{ id = 1001 }, "NW", 1, 2, 5, true, 50}, + [5] = {{ id = -1 }, "NW", 4, 1, 1, false, 100} }} else screen:expect([[ @@ -2824,8 +2845,8 @@ describe('float window', function() {1:yy }| {1:zz }| ]], float_pos={ - [4] = {{ id = 1001 }, "NW", 1, 2, 5, true}, - [5] = {{ id = -1 }, "NW", 2, 1, 0, false} + [4] = {{ id = 1001 }, "NW", 1, 2, 5, true, 50}, + [5] = {{ id = -1 }, "NW", 2, 1, 0, false, 100} }} else screen:expect([[ @@ -3086,7 +3107,7 @@ describe('float window', function() {1:word }| {1:longtext }| ]], float_pos={ - [4] = {{id = -1}, "NW", 2, 1, 0, false}} + [4] = {{id = -1}, "NW", 2, 1, 0, false, 100}} } else screen:expect([[ @@ -3130,8 +3151,8 @@ describe('float window', function() {15:some info }| {15:about item }| ]], float_pos={ - [4] = {{id = -1}, "NW", 2, 1, 0, false}, - [6] = {{id = 1002}, "NW", 2, 1, 12, true}, + [4] = {{id = -1}, "NW", 2, 1, 0, false, 100}, + [6] = {{id = 1002}, "NW", 2, 1, 12, true, 50}, }} else screen:expect([[ @@ -3245,7 +3266,7 @@ describe('float window', function() {1:word }| {1:longtext }| ]], float_pos={ - [4] = {{id = -1}, "NW", 2, 1, 0, false}, + [4] = {{id = -1}, "NW", 2, 1, 0, false, 100}, }} else screen:expect([[ @@ -6278,6 +6299,69 @@ describe('float window', function() ]]} end end) + + it('can use z-index', function() + local buf = meths.create_buf(false,false) + local win1 = meths.open_win(buf, false, {relative='editor', width=20, height=3, row=1, col=5, zindex=30}) + meths.win_set_option(win1, "winhl", "Normal:ErrorMsg,EndOfBuffer:ErrorMsg") + local win2 = meths.open_win(buf, false, {relative='editor', width=20, height=3, row=2, col=6, zindex=50}) + meths.win_set_option(win2, "winhl", "Normal:Search,EndOfBuffer:Search") + local win3 = meths.open_win(buf, false, {relative='editor', width=20, height=3, row=3, col=7, zindex=40}) + meths.win_set_option(win3, "winhl", "Normal:Question,EndOfBuffer:Question") + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + {7: }| + {7:~ }| + {7:~ }| + ## grid 5 + {17: }| + {17:~ }| + {17:~ }| + ## grid 6 + {8: }| + {8:~ }| + {8:~ }| + ]], float_pos={ + [4] = {{id = 1001}, "NW", 1, 1, 5, true, 30}; + [5] = {{id = 1002}, "NW", 1, 2, 6, true, 50}; + [6] = {{id = 1003}, "NW", 1, 3, 7, true, 40}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0}; + [6] = {win = {id = 1003}, topline = 0, botline = 2, curline = 0, curcol = 0}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }{7: }{0: }| + {0:~ }{7:~}{17: }{0: }| + {0:~ }{7:~}{17:~ }{8: }{0: }| + {0:~ }{17:~ }{8: }{0: }| + {0:~ }{8:~ }{0: }| + | + ]]} + end + end) end describe('with ext_multigrid', function() diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index fcf6926433..f73d051857 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -429,6 +429,15 @@ screen:redraw_debug() to show all intermediate screen states. ]]) extstate.win_viewport = nil end + if expected.float_pos then + expected.float_pos = deepcopy(expected.float_pos) + for _, v in pairs(expected.float_pos) do + if not v.external and v[7] == nil then + v[7] = 50 + end + end + end + -- Convert assertion errors into invalid screen state descriptions. for _, k in ipairs(concat_tables(ext_keys, {'mode', 'mouse_enabled'})) do -- Empty states are considered the default and need not be mentioned. @@ -1287,6 +1296,11 @@ function Screen:get_snapshot(attrs, ignore) end local function fmt_ext_state(name, state) + local function remove_all_metatables(item, path) + if path[#path] ~= inspect.METATABLE then + return item + end + end if name == "win_viewport" then local str = "{\n" for k,v in pairs(state) do @@ -1295,13 +1309,18 @@ local function fmt_ext_state(name, state) ..", curcol = "..v.curcol.."};\n") end return str .. "}" - else - -- TODO(bfredl): improve formatting of more states - local function remove_all_metatables(item, path) - if path[#path] ~= inspect.METATABLE then - return item + elseif name == "float_pos" then + local str = "{\n" + for k,v in pairs(state) do + str = str.." ["..k.."] = {{id = "..v[1].id.."}" + for i = 2, #v do + str = str..", "..inspect(v[i]) end + str = str .. "};\n" end + return str .. "}" + else + -- TODO(bfredl): improve formatting of more states return inspect(state,{process=remove_all_metatables}) end end diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 351f517945..e248232909 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -175,12 +175,12 @@ set(LUA_COMPAT53_SHA256 bec3a23114a3d9b3218038309657f0f506ad10dfbc03bb54e91da7e5 set(GPERF_URL https://github.com/neovim/deps/raw/ff5b4b18a87397a8564016071ae64f64bcd8c635/opt/gperf-3.1.tar.gz) set(GPERF_SHA256 588546b945bba4b70b6a3a616e80b4ab466e3f33024a352fc2198112cdbb3ae2) -# cat.exe curl.exe ca-bundle.crt diff.exe tee.exe xxd.exe -set(WINTOOLS_URL https://github.com/neovim/deps/raw/da3520b568054ce057e6168243ff50eea223bfa0/opt/win32tools.zip) -set(WINTOOLS_SHA256 190149d369ae1cd266bc39bceb2d1c061833a23640dfabd4089082c1a7824421) +# cat.exe curl.exe curl-ca-bundle.crt diff.exe tee.exe xxd.exe +set(WINTOOLS_URL https://github.com/neovim/deps/raw/9efd42511dcab26995fa3490f2319b270949159e/opt/win32tools.zip) +set(WINTOOLS_SHA256 378069d88a34e7f7283622213569020a2aba7a54f0b431fba28690f7eae3af9c) -set(WINGUI_URL https://github.com/equalsraf/neovim-qt/releases/download/v0.2.16/neovim-qt.zip) -set(WINGUI_SHA256 aad95a1f8413a9ebf36fc0298d0dfd7d786abf88cb0f4ae9f7ec895b70c7b312) +set(WINGUI_URL https://github.com/equalsraf/neovim-qt/releases/download/v0.2.16.1/neovim-qt.zip) +set(WINGUI_SHA256 ddb4492db03da407703fb0ab271c4eb060250d1a7d71200e2b3b981cb0de59de) set(WIN32YANK_X86_URL https://github.com/equalsraf/win32yank/releases/download/v0.0.4/win32yank-x86.zip) set(WIN32YANK_X86_SHA256 62f34e5a46c5d4a7b3f3b512e1ff7b77fedd432f42581cbe825233a996eed62c) |