diff options
43 files changed, 739 insertions, 406 deletions
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5ad4c90eaf..b72c2ab71d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -212,6 +212,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NVIM_VERSION: ${{ needs.linux.outputs.version }} + DEBUG: api run: | envsubst < "$GITHUB_WORKSPACE/.github/workflows/notes.md" > "$RUNNER_TEMP/notes.md" gh release create $TAG_NAME $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos/* nvim-linux64/* appimage/* nvim-win64/* diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim index 9933ace8c2..342731b272 100644 --- a/runtime/autoload/dist/ft.vim +++ b/runtime/autoload/dist/ft.vim @@ -829,6 +829,23 @@ func dist#ft#Dep3patch() endfor endfunc +" This function checks the first 15 lines for appearance of 'FoamFile' +" and then 'object' in a following line. +" In that case, it's probably an OpenFOAM file +func dist#ft#FTfoam() + let ffile = 0 + let lnum = 1 + while lnum <= 15 + if getline(lnum) =~# '^FoamFile' + let ffile = 1 + elseif ffile == 1 && getline(lnum) =~# '^\s*object' + setf foam + return + endif + let lnum = lnum + 1 + endwhile +endfunc + " Restore 'cpoptions' let &cpo = s:cpo_save unlet s:cpo_save diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index 7716af25bd..641cd93386 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -697,7 +697,8 @@ Line numbers may be specified with: *:range* *{address}* Each may be followed (several times) by '+' or '-' and an optional number. This number is added or subtracted from the preceding line number. If the -number is omitted, 1 is used. +number is omitted, 1 is used. If there is nothing before the '+' or '-' then +the current line is used. The "/" and "?" after {pattern} are required to separate the pattern from anything that follows. @@ -727,7 +728,7 @@ Some commands allow for a count after the command. This count is used as the number of lines to be used, starting with the line given in the last line specifier (the default is the cursor line). The commands that accept a count are the ones that use a range but do not have a file name argument (because -a file name can also be a number). +a file name can also be a number). The count cannot be negative. Examples: > :s/x/X/g 5 substitute 'x' by 'X' in the current line and four diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index bb36fa46f6..19db3158be 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -334,8 +334,9 @@ config({opts}, {namespace}) *vim.diagnostic.config()* that returns any of the above. Parameters: ~ - {opts} table Configuration table with the following - keys: + {opts} table|nil When omitted or "nil", retrieve the + current configuration. Otherwise, a + configuration table with the following keys: • underline: (default true) Use underline for diagnostics. Options: • severity: Only underline diagnostics diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index c2158f3a1e..fa75ead9a3 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -111,7 +111,7 @@ When mixing Number and Float the Number is converted to Float. Otherwise there is no automatic conversion of Float. You can use str2float() for String to Float, printf() for Float to String and float2nr() for Float to Number. - *E891* *E892* *E893* *E894* + *E362* *E891* *E892* *E893* *E894* *E907* When expecting a Float a Number can also be used, but nothing else. *no-type-checking* @@ -1195,6 +1195,7 @@ When expr8 is a |Funcref| type variable, invoke the function it refers to. expr8->name([args]) method call *method* *->* expr8->{lambda}([args]) + *E260* *E276* For methods that are also available as global functions this is the same as: > name(expr8 [, args]) There can also be methods specifically for the type of "expr8". @@ -4574,8 +4575,10 @@ funcref({name} [, {arglist}] [, {dict}]) function {name} is redefined later. Unlike |function()|, {name} must be an existing user function. - Also for autoloaded functions. {name} cannot be a builtin - function. + It only works for an autoloaded function if it has already + been loaded (to avoid mistakenly loading the autoload script + when only intending to use the function name, use |function()| + instead). {name} cannot be a builtin function. Can also be used as a |method|: > GetFuncname()->funcref([arg]) @@ -6892,7 +6895,7 @@ match({expr}, {pat} [, {start} [, {count}]]) *match()* GetText()->match('word') GetList()->match('word') < - *matchadd()* *E798* *E799* *E801* *E957* + *matchadd()* *E798* *E799* *E801* *E957* matchadd({group}, {pattern}[, {priority}[, {id} [, {dict}]]]) Defines a pattern to be highlighted in the current window (a "match"). It will be highlighted with {group}. Returns an @@ -11063,7 +11066,7 @@ See |:verbose-cmd| for more information. command, use line breaks instead of |:bar|: > :exe "func Foo()\necho 'foo'\nendfunc" < - *:delf* *:delfunction* *E130* *E131* *E933* + *:delf* *:delfunction* *E131* *E933* :delf[unction][!] {name} Delete function {name}. {name} can also be a |Dictionary| entry that is a @@ -13153,7 +13156,7 @@ code can be used: > unlet scriptnames_output ============================================================================== -The sandbox *eval-sandbox* *sandbox* *E48* +The sandbox *eval-sandbox* *sandbox* The 'foldexpr', 'formatexpr', 'includeexpr', 'indentexpr', 'statusline' and 'foldtext' options may be evaluated in a sandbox. This means that you are @@ -13162,6 +13165,7 @@ safety for when these options are set from a modeline. It is also used when the command from a tags file is executed and for CTRL-R = in the command line. The sandbox is also used for the |:sandbox| command. + *E48* These items are not allowed in the sandbox: - changing the buffer text - defining or changing mapping, autocommands, user commands diff --git a/runtime/doc/helphelp.txt b/runtime/doc/helphelp.txt index 9cc7d063a8..03ec8966d4 100644 --- a/runtime/doc/helphelp.txt +++ b/runtime/doc/helphelp.txt @@ -219,7 +219,7 @@ command: > < *:helpt* *:helptags* - *E154* *E150* *E151* *E152* *E153* *E670* *E856* + *E150* *E151* *E152* *E153* *E154* *E670* *E856* :helpt[ags] [++t] {dir} Generate the help tags file(s) for directory {dir}. When {dir} is ALL then all "doc" directories in diff --git a/runtime/doc/if_cscop.txt b/runtime/doc/if_cscop.txt index f05b3bb8ed..8947aefc1b 100644 --- a/runtime/doc/if_cscop.txt +++ b/runtime/doc/if_cscop.txt @@ -40,7 +40,7 @@ See |cscope-usage| to get started. ============================================================================== Cscope commands *cscope-commands* - *:cscope* *:cs* *:scs* *:scscope* *E259* *E262* *E561* *E560* + *:cscope* *:cs* *:scs* *:scscope* *E259* *E262* *E560* *E561* All cscope commands are accessed through suboptions to the cscope commands. `:cscope` or `:cs` is the main command `:scscope` or `:scs` does the same and splits the window diff --git a/runtime/doc/indent.txt b/runtime/doc/indent.txt index 1b42092616..a76f8636f8 100644 --- a/runtime/doc/indent.txt +++ b/runtime/doc/indent.txt @@ -872,7 +872,7 @@ For example, with N = 1, this will give: *PHP_outdentphpescape* To indent PHP escape tags as the surrounding non-PHP code (only affects the PHP escape tags): > -:let g:PHP_outdentphpescape = 0 + :let g:PHP_outdentphpescape = 0 ------------- *PHP_removeCRwhenUnix* @@ -1199,7 +1199,7 @@ comments will be indented according to the correctly indented code. VIM *ft-vim-indent* - + *g:vim_indent_cont* For indenting Vim scripts there is one variable that specifies the amount of indent for a continuation line, a line that starts with a backslash: > diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index d74d7cafa8..ae2b9c4418 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -830,7 +830,7 @@ space is preferred). Maximum line length is 510 bytes. For an example, imagine the 'thesaurus' file has a line like this: > angry furious mad enraged -<Placing the cursor after the letters "ang" and typing CTRL-X CTRL-T would +Placing the cursor after the letters "ang" and typing CTRL-X CTRL-T would complete the word "angry"; subsequent presses would change the word to "furious", "mad" etc. @@ -842,7 +842,7 @@ https://github.com/vim/vim/issues/629#issuecomment-443293282 Unpack thesaurus_pkg.zip, put the thesaurus.txt file somewhere, e.g. ~/.vim/thesaurus/english.txt, and the 'thesaurus' option to this file name. - + Completing keywords with 'thesaurusfunc' *compl-thesaurusfunc* If the 'thesaurusfunc' option is set, then the user specified function is diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index f6d9e45d64..e2c274e040 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -82,8 +82,7 @@ modes. map command applies. The mapping may remain defined for other modes where it applies. It also works when {lhs} matches the {rhs} of a - mapping. This is for when when an abbreviation - applied. + mapping. This is for when an abbreviation applied. Note: Trailing spaces are included in the {lhs}. This unmap does NOT work: > :map @@ foo diff --git a/runtime/doc/mbyte.txt b/runtime/doc/mbyte.txt index 3bbf36c642..2aa49cee1e 100644 --- a/runtime/doc/mbyte.txt +++ b/runtime/doc/mbyte.txt @@ -489,8 +489,8 @@ Use the RPM or port for your system. window specific to the input method. -USING XIM *multibyte-input* *E284* *E286* *E287* *E288* - *E285* *E289* +USING XIM *multibyte-input* *E284* *E285* *E286* *E287* + *E288* *E289* Note that Display and Input are independent. It is possible to see your language even though you have no input method for it. But when your Display diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt index 6fbd9ec922..950028d9cc 100644 --- a/runtime/doc/message.txt +++ b/runtime/doc/message.txt @@ -112,7 +112,8 @@ wiped out a buffer which contains a mark or is referenced in another way. *E95* > Buffer with this name already exists -You cannot have two buffers with the same name. +You cannot have two buffers with exactly the same name. This includes the +path leading to the file. *E72* > Close error on swap file @@ -513,10 +514,10 @@ If you type "gq", it will execute this mapping, which will call "gq" again. *E22* > Scripts nested too deep -Scripts can be read with the "-s" command-line argument and with the ":source" -command. The script can then again read another script. This can continue -for about 14 levels. When more nesting is done, Vim assumes that there is a -recursive loop somewhere and stops with this error message. +Scripts can be read with the "-s" command-line argument and with the +`:source!` command. The script can then again read another script. This can +continue for about 14 levels. When more nesting is done, Vim assumes that +there is a recursive loop and stops with this error message. *E300* > Swap file already exists (symlink attack?) @@ -686,6 +687,7 @@ Ex command or function was given an invalid argument. Or |jobstart()| or Trailing characters An argument was given to an Ex command that does not permit one. +Or the argument has invalid characters and has not been recognized. *E477* *E478* > No ! allowed diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index c929178f5a..06236741c2 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -304,7 +304,7 @@ value to the local value, it doesn't switch back to using the global value This will make the local value of 'path' empty, so that the global value is used. Thus it does the same as: > :setlocal path= -Note: In the future more global options can be made global-local. Using +Note: In the future more global options can be made |global-local|. Using ":setlocal" on a global option might work differently then. @@ -686,10 +686,10 @@ A jump table for the options with a short description can be found at |Q_op|. Write the contents of the file, if it has been modified, on each `:next`, `:rewind`, `:last`, `:first`, `:previous`, `:stop`, `:suspend`, `:tag`, `:!`, `:make`, CTRL-] and CTRL-^ command; and when - a :buffer, CTRL-O, CTRL-I, '{A-Z0-9}, or `{A-Z0-9} command takes one + a `:buffer`, CTRL-O, CTRL-I, '{A-Z0-9}, or `{A-Z0-9} command takes one to another file. A buffer is not written if it becomes hidden, e.g. when 'bufhidden' is - set to "hide" and `:next` is used + set to "hide" and `:next` is used. Note that for some commands the 'autowrite' option is not used, see 'autowriteall' for that. Some buffers will not be written, specifically when 'buftype' is @@ -5131,7 +5131,8 @@ A jump table for the options with a short description can be found at |Q_op|. Don't include both "curdir" and "sesdir". When neither is included filenames are stored as absolute paths. - + If you leave out "options" many things won't work well after restoring + the session. *'shada'* *'sd'* *E526* *E527* *E528* 'shada' 'sd' string (Vim default for Win32: !,'100,<50,s10,h,rA:,rB: diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt index dfed39dba6..634145da3e 100644 --- a/runtime/doc/pattern.txt +++ b/runtime/doc/pattern.txt @@ -304,7 +304,7 @@ the pattern. ============================================================================== 2. The definition of a pattern *search-pattern* *pattern* *[pattern]* *regular-expression* *regexp* *Pattern* - *E76* *E383* *E476* + *E383* *E476* For starters, read chapter 27 of the user manual |usr_27.txt|. @@ -1036,6 +1036,8 @@ match ASCII characters, as indicated by the range. \(\) A pattern enclosed by escaped parentheses. */\(* */\(\)* */\)* E.g., "\(^a\)" matches 'a' at the start of a line. + There can only be ten of these. You can use "\%(" to add more, but + not counting it as a sub-expression. *E51* *E54* *E55* *E872* *E873* \1 Matches the same string that was matched by */\1* *E65* @@ -1058,7 +1060,7 @@ x A single character, with no special meaning, matches itself \x A backslash followed by a single character, with no special meaning, is reserved for future expansions -[] (with 'nomagic': \[]) */[]* */\[]* */\_[]* */collection* +[] (with 'nomagic': \[]) */[]* */\[]* */\_[]* */collection* *E76* \_[] A collection. This is a sequence of characters enclosed in square brackets. It matches any single character in the collection. diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index ba1da209f7..873fbd8971 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -232,7 +232,7 @@ processing a quickfix or location list command, it will be aborted. ":qall!" |:qall|, except that Nvim exits non-zero or [count]. - *:cf* *:cfile* + *:cf* *:cfi* *:cfile* :cf[ile][!] [errorfile] Read the error file and jump to the first error. This is done automatically when Vim is started with the -q option. You can use this command when you diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 0fd481cd83..2c5531411d 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -1406,7 +1406,7 @@ add the following line to your startup file: > :let g:filetype_euphoria = "euphoria4" -Elixir and Euphoria share the *.ex file extension. If the filetype is +Elixir and Euphoria share the *.ex file extension. If the filetype is specifically set as Euphoria with the g:filetype_euphoria variable, or the file is determined to be Euphoria based on keywords in the file, then the filetype will be set as Euphoria. Otherwise, the filetype will default to @@ -1437,7 +1437,7 @@ The following file extensions are auto-detected as Elixir file types: *.ex, *.exs, *.eex, *.leex, *.lock -Elixir and Euphoria share the *.ex file extension. If the filetype is +Elixir and Euphoria share the *.ex file extension. If the filetype is specifically set as Euphoria with the g:filetype_euphoria variable, or the file is determined to be Euphoria based on keywords in the file, then the filetype will be set as Euphoria. Otherwise, the filetype will default to diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 8a4468a130..05bffc3038 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -249,7 +249,7 @@ g8 Print the hex values of the bytes used in the To enter |Terminal-mode| automatically: > autocmd TermOpen * startinsert < - *:!cmd* *:!* *E34* + *:!cmd* *:!* :!{cmd} Execute {cmd} with 'shell'. See also |:terminal|. The command runs in a non-interactive shell connected @@ -261,6 +261,7 @@ g8 Print the hex values of the bytes used in the Use |jobstart()| instead. > :call jobstart('foo', {'detach':1}) < + *E34* Any "!" in {cmd} is replaced with the previous external command (see also 'cpoptions'), unless escaped by a backslash. Example: ":!ls" followed by diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index bb31895c96..5b91321c40 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -124,7 +124,7 @@ highlight group (|hl-EndOfBuffer|) can be used to change the highlighting of the filler characters. ============================================================================== -3. Opening and closing a window *opening-window* *E36* +3. Opening and closing a window *opening-window* CTRL-W s *CTRL-W_s* CTRL-W S *CTRL-W_S* @@ -223,6 +223,10 @@ CTRL-W ge *CTRL-W_ge* Note that the 'splitbelow' and 'splitright' options influence where a new window will appear. + *E36* +Creating a window will fail if there is not enough room. Every window needs +at least one screen line and column, sometimes more. Options 'winminheight' +and 'winminwidth' are relevant. *:vert* *:vertical* :vert[ical] {cmd} diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 907f806f31..aa12a5f8dd 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -1,7 +1,7 @@ " Vim support file to detect file types " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2021 Dec 27 +" Last Change: 2022 Jan 05 " Listen very carefully, I will say this only once if exists("did_load_filetypes") @@ -1210,6 +1210,9 @@ au BufNewFile,BufRead *.xom,*.xin setf omnimark " OPAM au BufNewFile,BufRead opam,*.opam,*.opam.template setf opam +" OpenFOAM +au BufNewFile,BufRead [a-zA-Z0-9]*Dict\(.*\)\=,[a-zA-Z]*Properties\(.*\)\=,*Transport\(.*\),fvSchemes,fvSolution,fvConstrains,fvModels,*/constant/g,*/0\(\.orig\)\=/* call dist#ft#FTfoam() + " OpenROAD au BufNewFile,BufRead *.or setf openroad diff --git a/runtime/ftplugin/git.vim b/runtime/ftplugin/git.vim deleted file mode 100644 index 75b20f021e..0000000000 --- a/runtime/ftplugin/git.vim +++ /dev/null @@ -1,41 +0,0 @@ -" Vim filetype plugin -" Language: generic git output -" Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2019 Dec 05 - -" Only do this when not done yet for this buffer -if (exists("b:did_ftplugin")) - finish -endif -let b:did_ftplugin = 1 - -if !exists('b:git_dir') - if expand('%:p') =~# '[\/]\.git[\/]modules[\/]\|:[\/][\/]\|^\a\a\+:' - " Stay out of the way - elseif expand('%:p') =~# '[\/]\.git[\/]worktrees' - let b:git_dir = matchstr(expand('%:p'),'.*\.git[\/]worktrees[\/][^\/]\+\>') - elseif expand('%:p') =~# '\.git\>' - let b:git_dir = matchstr(expand('%:p'),'.*\.git\>') - elseif $GIT_DIR != '' - let b:git_dir = $GIT_DIR - endif - if (has('win32') || has('win64')) && exists('b:git_dir') - let b:git_dir = substitute(b:git_dir,'\\','/','g') - endif -endif - -if exists('*shellescape') && exists('b:git_dir') && b:git_dir != '' - if b:git_dir =~# '/\.git$' " Not a bare repository - let &l:path = escape(fnamemodify(b:git_dir,':h'),'\, ').','.&l:path - endif - let &l:path = escape(b:git_dir,'\, ').','.&l:path - let &l:keywordprg = 'git --git-dir='.shellescape(b:git_dir).' show' -else - setlocal keywordprg=git\ show -endif -if has('gui_running') - let &l:keywordprg = substitute(&l:keywordprg,'^git\>','git --no-pager','') -endif - -setlocal includeexpr=substitute(v:fname,'^[^/]\\+/','','') -let b:undo_ftplugin = "setl keywordprg< path< includeexpr<" diff --git a/runtime/ftplugin/gitcommit.vim b/runtime/ftplugin/gitcommit.vim index 9b1998acaa..9342799b56 100644 --- a/runtime/ftplugin/gitcommit.vim +++ b/runtime/ftplugin/gitcommit.vim @@ -1,66 +1,57 @@ " Vim filetype plugin " Language: git commit file " Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2019 Dec 05 +" Last Change: 2022 Jan 05 " Only do this when not done yet for this buffer if (exists("b:did_ftplugin")) finish endif -runtime! ftplugin/git.vim let b:did_ftplugin = 1 -setlocal comments=:# commentstring=#\ %s setlocal nomodeline tabstop=8 formatoptions+=tl textwidth=72 setlocal formatoptions-=c formatoptions-=r formatoptions-=o formatoptions-=q formatoptions+=n setlocal formatlistpat+=\\\|^\\s*[-*+]\\s\\+ +setlocal include=^+++ +setlocal includeexpr=substitute(v:fname,'^[bi]/','','') -let b:undo_ftplugin = 'setl modeline< tabstop< formatoptions< tw< com< cms< formatlistpat<' +let b:undo_ftplugin = 'setl modeline< tabstop< formatoptions< tw< com< cms< formatlistpat< inc< inex<' -if exists("g:no_gitcommit_commands") || v:version < 700 - finish -endif +let s:l = search('\C\m^[#;@!$%^&|:] -\{24,\} >8 -\{24,\}$', 'cnW', '', 100) +let &l:comments = ':' . (matchstr(getline(s:l ? s:l : '$'), '^[#;@!$%^&|:]\S\@!') . '#')[0] +let &l:commentstring = &l:comments[1] . ' %s' +unlet s:l -if !exists("b:git_dir") - let b:git_dir = expand("%:p:h") +if exists("g:no_gitcommit_commands") + finish endif -command! -bang -bar -buffer -complete=custom,s:diffcomplete -nargs=* DiffGitCached :call s:gitdiffcached(<bang>0,b:git_dir,<f-args>) +command! -bang -bar -buffer -complete=custom,s:diffcomplete -nargs=* DiffGitCached :call s:gitdiffcached(<bang>0, <f-args>) let b:undo_ftplugin = b:undo_ftplugin . "|delc DiffGitCached" -function! s:diffcomplete(A,L,P) +function! s:diffcomplete(A, L, P) abort let args = "" if a:P <= match(a:L." -- "," -- ")+3 let args = args . "-p\n--stat\n--shortstat\n--summary\n--patch-with-stat\n--no-renames\n-B\n-M\n-C\n" end - if exists("b:git_dir") && a:A !~ '^-' - let tree = fnamemodify(b:git_dir,':h') - if strpart(getcwd(),0,strlen(tree)) == tree - let args = args."\n".system("git diff --cached --name-only") - endif + if a:A !~ '^-' && !empty(getftype('.git')) + let args = args."\n".system("git diff --cached --name-only") endif return args endfunction -function! s:gitdiffcached(bang,gitdir,...) - let tree = fnamemodify(a:gitdir,':h') +function! s:gitdiffcached(bang, ...) abort let name = tempname() - let git = "git" - if strpart(getcwd(),0,strlen(tree)) != tree - let git .= " --git-dir=".(exists("*shellescape") ? shellescape(a:gitdir) : '"'.a:gitdir.'"') - endif if a:0 - let extra = join(map(copy(a:000),exists("*shellescape") ? 'shellescape(v:val)' : "'\"'.v:val.'\"'")) + let extra = join(map(copy(a:000), 'shellescape(v:val)')) else let extra = "-p --stat=".&columns endif - call system(git." diff --cached --no-color --no-ext-diff ".extra." > ".(exists("*shellescape") ? shellescape(name) : name)) - exe "pedit ".(exists("*fnameescape") ? fnameescape(name) : name) + call system("git diff --cached --no-color --no-ext-diff ".extra." > ".shellescape(name)) + exe "pedit " . fnameescape(name) wincmd P - let b:git_dir = a:gitdir - command! -bang -bar -buffer -complete=custom,s:diffcomplete -nargs=* DiffGitCached :call s:gitdiffcached(<bang>0,b:git_dir,<f-args>) - nnoremap <buffer> <silent> q :q<CR> + command! -bang -bar -buffer -complete=custom,s:diffcomplete -nargs=* DiffGitCached :call s:gitdiffcached(<bang>0, <f-args>) setlocal buftype=nowrite nobuflisted noswapfile nomodifiable filetype=git endfunction diff --git a/runtime/ftplugin/gitrebase.vim b/runtime/ftplugin/gitrebase.vim index 2fed53c829..143f86a251 100644 --- a/runtime/ftplugin/gitrebase.vim +++ b/runtime/ftplugin/gitrebase.vim @@ -1,22 +1,20 @@ " Vim filetype plugin " Language: git rebase --interactive " Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2019 Dec 05 +" Last Change: 2022 Jan 05 " Only do this when not done yet for this buffer if (exists("b:did_ftplugin")) finish endif -runtime! ftplugin/git.vim let b:did_ftplugin = 1 -setlocal comments=:# commentstring=#\ %s formatoptions-=t +let &l:comments = ':' . (matchstr(getline('$'), '^[#;@!$%^&|:]\S\@!') . '#')[0] +let &l:commentstring = &l:comments[1] . ' %s' +setlocal formatoptions-=t setlocal nomodeline -if !exists("b:undo_ftplugin") - let b:undo_ftplugin = "" -endif -let b:undo_ftplugin = b:undo_ftplugin."|setl com< cms< fo< ml<" +let b:undo_ftplugin = "setl com< cms< fo< ml<" function! s:choose(word) abort s/^\(\w\+\>\)\=\(\s*\)\ze\x\{4,40\}\>/\=(strlen(submatch(1)) == 1 ? a:word[0] : a:word) . substitute(submatch(2),'^$',' ','')/e @@ -41,8 +39,7 @@ if exists("g:no_plugin_maps") || exists("g:no_gitrebase_maps") finish endif -nnoremap <buffer> <expr> K col('.') < 7 && expand('<Lt>cword>') =~ '\X' && getline('.') =~ '^\w\+\s\+\x\+\>' ? 'wK' : 'K' nnoremap <buffer> <silent> <C-A> :<C-U><C-R>=v:count1<CR>Cycle<CR> nnoremap <buffer> <silent> <C-X> :<C-U><C-R>=v:count1<CR>Cycle!<CR> -let b:undo_ftplugin = b:undo_ftplugin . "|exe 'nunmap <buffer> K'|exe 'nunmap <buffer> <C-A>'|exe 'nunmap <buffer> <C-X>'" +let b:undo_ftplugin = b:undo_ftplugin . "|exe 'nunmap <buffer> <C-A>'|exe 'nunmap <buffer> <C-X>'" diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 417b661155..b4537c2882 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -30,7 +30,7 @@ M.handlers = setmetatable({}, { __newindex = function(t, name, handler) vim.validate { handler = {handler, "t" } } rawset(t, name, handler) - if not global_diagnostic_options[name] then + if global_diagnostic_options[name] == nil then global_diagnostic_options[name] = true end end, @@ -552,7 +552,8 @@ end --- - `table`: Enable this feature with overrides. Use an empty table to use default values. --- - `function`: Function with signature (namespace, bufnr) that returns any of the above. --- ----@param opts table Configuration table with the following keys: +---@param opts table|nil When omitted or "nil", retrieve the current configuration. Otherwise, a +--- configuration table with the following keys: --- - underline: (default true) Use underline for diagnostics. Options: --- * severity: Only underline diagnostics matching the given severity --- |diagnostic-severity| @@ -599,7 +600,7 @@ end --- global diagnostic options. function M.config(opts, namespace) vim.validate { - opts = { opts, 't' }, + opts = { opts, 't', true }, namespace = { namespace, 'n', true }, } @@ -611,10 +612,13 @@ function M.config(opts, namespace) t = global_diagnostic_options end - for opt in pairs(global_diagnostic_options) do - if opts[opt] ~= nil then - t[opt] = opts[opt] - end + if not opts then + -- Return current config + return vim.deepcopy(t) + end + + for k, v in pairs(opts) do + t[k] = v end if namespace then @@ -823,6 +827,7 @@ M.handlers.signs = { } bufnr = get_bufnr(bufnr) + opts = opts or {} if opts.signs and opts.signs.severity then diagnostics = filter_by_severity(opts.signs.severity, diagnostics) @@ -890,6 +895,7 @@ M.handlers.underline = { } bufnr = get_bufnr(bufnr) + opts = opts or {} if opts.underline and opts.underline.severity then diagnostics = filter_by_severity(opts.underline.severity, diagnostics) @@ -942,6 +948,7 @@ M.handlers.virtual_text = { } bufnr = get_bufnr(bufnr) + opts = opts or {} local severity if opts.virtual_text then diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 28cffdf072..3d91abc406 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -860,6 +860,10 @@ local filename = { ["exim.conf"] = "exim", exports = "exports", [".fetchmailrc"] = "fetchmail", + fvSchemes = function() vim.fn["dist#ft#FTfoam"]() end, + fvSolution = function() vim.fn["dist#ft#FTfoam"]() end, + fvConstraints = function() vim.fn["dist#ft#FTfoam"]() end, + fvModels = function() vim.fn["dist#ft#FTfoam"]() end, fstab = "fstab", mtab = "fstab", [".gdbinit"] = "gdb", @@ -1334,6 +1338,14 @@ local pattern = { ["zlog.*"] = starsetf('zsh'), ["zsh.*"] = starsetf('zsh'), ["ae%d+%.txt"] = 'mail', + ["[a-zA-Z0-9]*Dict"] = function() vim.fn["dist#ft#FTfoam"]() end, + ["[a-zA-Z0-9]*Dict%..*"] = function() vim.fn["dist#ft#FTfoam"]() end, + ["[a-zA-Z]*Properties"] = function() vim.fn["dist#ft#FTfoam"]() end, + ["[a-zA-Z]*Properties%..*"] = function() vim.fn["dist#ft#FTfoam"]() end, + [".*Transport%..*"] = function() vim.fn["dist#ft#FTfoam"]() end, + [".*/constant/g"] = function() vim.fn["dist#ft#FTfoam"]() end, + [".*/0/.*"] = function() vim.fn["dist#ft#FTfoam"]() end, + [".*/0%.orig/.*"] = function() vim.fn["dist#ft#FTfoam"]() end, -- END PATTERN } -- luacheck: pop diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index fc9c991c05..8b7eb4ac90 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -312,56 +312,72 @@ do --- client_id → state --- --- state + --- use_incremental_sync: bool + --- buffers: bufnr -> buffer_state + --- + --- buffer_state --- pending_change?: function that the timer starts to trigger didChange --- pending_changes: table (uri -> list of pending changeset tables)); - -- Only set if incremental_sync is used - --- use_incremental_sync: bool - --- buffers?: table (bufnr → lines); for incremental sync only + --- Only set if incremental_sync is used + --- --- timer?: uv_timer + --- lines: table local state_by_client = {} ---@private function changetracking.init(client, bufnr) + local use_incremental_sync = ( + if_nil(client.config.flags.allow_incremental_sync, true) + and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental + ) local state = state_by_client[client.id] if not state then state = { - pending_changes = {}; - last_flush = {}; - use_incremental_sync = ( - if_nil(client.config.flags.allow_incremental_sync, true) - and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental - ); + buffers = {}; + debounce = client.config.flags.debounce_text_changes or 150, + use_incremental_sync = use_incremental_sync; } state_by_client[client.id] = state end - if not state.use_incremental_sync then - return - end - if not state.buffers then - state.buffers = {} + if not state.buffers[bufnr] then + local buf_state = {} + state.buffers[bufnr] = buf_state + if use_incremental_sync then + buf_state.lines = nvim_buf_get_lines(bufnr, 0, -1, true) + buf_state.pending_changes = {} + end end - state.buffers[bufnr] = nvim_buf_get_lines(bufnr, 0, -1, true) end ---@private function changetracking.reset_buf(client, bufnr) - changetracking.flush(client) + changetracking.flush(client, bufnr) local state = state_by_client[client.id] - if state then - if state.buffers then - state.buffers[bufnr] = nil + if state and state.buffers then + local buf_state = state.buffers[bufnr] + state.buffers[bufnr] = nil + if buf_state and buf_state.timer then + buf_state.timer:stop() + buf_state.timer:close() + buf_state.timer = nil end - state.last_flush = {} end end ---@private function changetracking.reset(client_id) local state = state_by_client[client_id] - if state then - state_by_client[client_id] = nil - changetracking._reset_timer(state) + if not state then + return end + for _, buf_state in pairs(state.buffers) do + if buf_state.timer then + buf_state.timer:stop() + buf_state.timer:close() + buf_state.timer = nil + end + end + state.buffers = {} end ---@private @@ -371,35 +387,27 @@ do -- debounce can be skipped and otherwise maybe reduced. -- -- This turns the debounce into a kind of client rate limiting - local function next_debounce(debounce, state, bufnr) + local function next_debounce(debounce, buf_state) if debounce == 0 then return 0 end local ns_to_ms = 0.000001 - local last_flush = state.last_flush[bufnr] - if not last_flush then + if not buf_state.last_flush then return debounce end local now = uv.hrtime() - local ms_since_last_flush = (now - last_flush) * ns_to_ms - local remaining_debounce = debounce - ms_since_last_flush - if remaining_debounce > 0 then - return remaining_debounce - else - state.last_flush[bufnr] = now - return 0 - end + local ms_since_last_flush = (now - buf_state.last_flush) * ns_to_ms + return math.max(debounce - ms_since_last_flush, 0) end ---@private function changetracking.prepare(bufnr, firstline, lastline, new_lastline) - local incremental_changes = function(client) - local cached_buffers = state_by_client[client.id].buffers + local incremental_changes = function(client, buf_state) local curr_lines = nvim_buf_get_lines(bufnr, 0, -1, true) local line_ending = buf_get_line_ending(bufnr) local incremental_change = sync.compute_diff( - cached_buffers[bufnr], curr_lines, firstline, lastline, new_lastline, client.offset_encoding or 'utf-16', line_ending) - cached_buffers[bufnr] = curr_lines + buf_state.lines, curr_lines, firstline, lastline, new_lastline, client.offset_encoding or 'utf-16', line_ending) + buf_state.lines = curr_lines return incremental_change end local full_changes = once(function() @@ -413,79 +421,68 @@ do return end local state = state_by_client[client.id] - changetracking._reset_timer(state) - local debounce = next_debounce(client.config.flags.debounce_text_changes or 150, state, bufnr) - if debounce == 0 then - if state.pending_change then - state.pending_change() - end - local changes = state.use_incremental_sync and incremental_changes(client) or full_changes() - client.notify("textDocument/didChange", { - textDocument = { - uri = uri; - version = util.buf_versions[bufnr]; - }; - contentChanges = { changes, } - }) - return - end + local buf_state = state.buffers[bufnr] + changetracking._reset_timer(buf_state) + local debounce = next_debounce(state.debounce, buf_state) if state.use_incremental_sync then -- This must be done immediately and cannot be delayed -- The contents would further change and startline/endline may no longer fit - if not state.pending_changes[uri] then - state.pending_changes[uri] = {} - end - table.insert(state.pending_changes[uri], incremental_changes(client)) + table.insert(buf_state.pending_changes, incremental_changes(client, buf_state)) end - state.pending_change = function() - state.pending_change = nil - state.last_flush[bufnr] = uv.hrtime() + buf_state.pending_change = function() + buf_state.pending_change = nil + buf_state.last_flush = uv.hrtime() if client.is_stopped() or not vim.api.nvim_buf_is_valid(bufnr) then return end - if state.use_incremental_sync then - for change_uri, content_changes in pairs(state.pending_changes) do - client.notify("textDocument/didChange", { - textDocument = { - uri = change_uri; - version = util.buf_versions[vim.uri_to_bufnr(change_uri)]; - }; - contentChanges = content_changes, - }) - end - state.pending_changes = {} - else - client.notify("textDocument/didChange", { - textDocument = { - uri = uri; - version = util.buf_versions[bufnr]; - }; - contentChanges = { full_changes() }, - }) - end + local changes = state.use_incremental_sync and buf_state.pending_changes or { full_changes() } + client.notify("textDocument/didChange", { + textDocument = { + uri = uri, + version = util.buf_versions[bufnr], + }, + contentChanges = changes, + }) + buf_state.pending_changes = {} + end + if debounce == 0 then + buf_state.pending_change() + else + local timer = vim.loop.new_timer() + buf_state.timer = timer + -- Must use schedule_wrap because `full_changes()` calls nvim_buf_get_lines + timer:start(debounce, 0, vim.schedule_wrap(buf_state.pending_change)) end - state.timer = vim.loop.new_timer() - -- Must use schedule_wrap because `full_changes()` calls nvim_buf_get_lines - state.timer:start(debounce, 0, vim.schedule_wrap(state.pending_change)) end end - function changetracking._reset_timer(state) - if state.timer then - state.timer:stop() - state.timer:close() - state.timer = nil + function changetracking._reset_timer(buf_state) + if buf_state.timer then + buf_state.timer:stop() + buf_state.timer:close() + buf_state.timer = nil end end --- Flushes any outstanding change notification. ---@private - function changetracking.flush(client) + function changetracking.flush(client, bufnr) local state = state_by_client[client.id] - if state then - changetracking._reset_timer(state) - if state.pending_change then - state.pending_change() + if not state then + return + end + if bufnr then + local buf_state = state.buffers[bufnr] or {} + changetracking._reset_timer(buf_state) + if buf_state.pending_change then + buf_state.pending_change() + end + else + for _, buf_state in pairs(state.buffers) do + changetracking._reset_timer(buf_state) + if buf_state.pending_change then + buf_state.pending_change() + end end end end @@ -991,7 +988,7 @@ function lsp.start_client(config) or error(string.format("not found: %q request handler for client %q.", method, client.name)) end -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state - changetracking.flush(client) + changetracking.flush(client, bufnr) bufnr = resolve_bufnr(bufnr) local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr) local success, request_id = rpc.request(method, params, function(err, result) @@ -1048,14 +1045,16 @@ function lsp.start_client(config) ---@private --- Sends a notification to an LSP server. --- - ---@param method (string) LSP method name. - ---@param params (optional, table) LSP request params. - ---@param bufnr (number) Buffer handle, or 0 for current. + ---@param method string LSP method name. + ---@param params table|nil LSP request params. ---@returns {status} (bool) true if the notification was successful. ---If it is false, then it will always be false ---(the client has shutdown). - function client.notify(...) - return rpc.notify(...) + function client.notify(method, params) + if method ~= 'textDocument/didChange' then + changetracking.flush(client) + end + return rpc.notify(method, params) end ---@private diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index c1d777ae6c..e8633f5924 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -503,7 +503,7 @@ local function on_code_action_results(results, ctx) ---@private local function apply_action(action, client) if action.edit then - util.apply_workspace_edit(action.edit) + util.apply_workspace_edit(action.edit, client.offset_encoding) end if action.command then local command = type(action.command) == 'table' and action.command or action diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index a48302cc4b..6378ed0749 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -111,13 +111,15 @@ M['client/registerCapability'] = function(_, _, ctx) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit -M['workspace/applyEdit'] = function(_, workspace_edit) +M['workspace/applyEdit'] = function(_, workspace_edit, ctx) if not workspace_edit then return end -- TODO(ashkan) Do something more with label? + local client_id = ctx.client_id + local client = vim.lsp.get_client_by_id(client_id) if workspace_edit.label then print("Workspace edit", workspace_edit.label) end - local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit) + local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit, client.offset_encoding) return { applied = status; failureReason = result; @@ -159,6 +161,28 @@ M['textDocument/codeLens'] = function(...) end +--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references +M['textDocument/references'] =function(_, result, ctx, config) + if not result or vim.tbl_isempty(result) then + vim.notify('No references found') + else + config = config or {} + if config.loclist then + vim.fn.setloclist(0, {}, ' ', { + title = 'Language Server'; + items = util.locations_to_items(result, ctx.offset_encoding); + }) + api.nvim_command("lopen") + else + vim.fn.setqflist({}, ' ', { + title = 'Language Server'; + items = util.locations_to_items(result, ctx.offset_encoding); + }) + api.nvim_command("botright copen") + end + end +end + ---@private --- Return a function that converts LSP responses to list items and opens the list @@ -194,9 +218,6 @@ local function response_to_list(map_result, entity) end ---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references -M['textDocument/references'] = response_to_list(util.locations_to_items, 'references') - --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol M['textDocument/documentSymbol'] = response_to_list(util.symbols_to_items, 'document symbols') @@ -277,19 +298,23 @@ local function location_handler(_, result, ctx, _) local _ = log.info() and log.info(ctx.method, 'No location found') return nil end + local client = vim.lsp.get_client_by_id(ctx.client_id) -- textDocument/definition can return Location or Location[] -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition if vim.tbl_islist(result) then - util.jump_to_location(result[1]) + util.jump_to_location(result[1], client.offset_encoding) if #result > 1 then - vim.fn.setqflist({}, ' ', {title = 'LSP locations', items = util.locations_to_items(result)}) + vim.fn.setqflist({}, ' ', { + title = 'LSP locations', + items = util.locations_to_items(result, client.offset_encoding) + }) api.nvim_command("copen") end else - util.jump_to_location(result) + util.jump_to_location(result, client.offset_encoding) end end diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 89c5ebe8f7..d09865be89 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -277,7 +277,7 @@ end ---@private --- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position --- Returns a zero-indexed column, since set_lines() does the conversion to ----@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to utf-16 +---@param offset_encoding string utf-8|utf-16|utf-32 --- 1-indexed local function get_line_byte_from_position(bufnr, position, offset_encoding) -- LSP's line and characters are 0-indexed @@ -286,7 +286,7 @@ local function get_line_byte_from_position(bufnr, position, offset_encoding) -- When on the first character, we can ignore the difference between byte and -- character if col > 0 then - local line = get_line(bufnr, position.line) + local line = get_line(bufnr, position.line) or '' local ok, result ok, result = pcall(_str_byteindex_enc, line, col, offset_encoding) if ok then @@ -360,15 +360,14 @@ end --- Applies a list of text edits to a buffer. ---@param text_edits table list of `TextEdit` objects ---@param bufnr number Buffer id ----@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to encoding of first client of `bufnr` +---@param offset_encoding string utf-8|utf-16|utf-32 defaults to encoding of first client of `bufnr` ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit function M.apply_text_edits(text_edits, bufnr, offset_encoding) validate { text_edits = { text_edits, 't', false }; bufnr = { bufnr, 'number', false }; - offset_encoding = { offset_encoding, 'string', true }; + offset_encoding = { offset_encoding, 'string', false }; } - offset_encoding = offset_encoding or M._get_offset_encoding(bufnr) if not next(text_edits) then return end if not api.nvim_buf_is_loaded(bufnr) then vim.fn.bufload(bufnr) @@ -402,25 +401,6 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) end end) - -- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't accept it so we should fix it here. - local has_eol_text_edit = false - local max = vim.api.nvim_buf_line_count(bufnr) - local len = _str_utfindex_enc(vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '', nil, offset_encoding) - text_edits = vim.tbl_map(function(text_edit) - if max <= text_edit.range.start.line then - text_edit.range.start.line = max - 1 - text_edit.range.start.character = len - text_edit.newText = '\n' .. text_edit.newText - has_eol_text_edit = true - end - if max <= text_edit.range['end'].line then - text_edit.range['end'].line = max - 1 - text_edit.range['end'].character = len - has_eol_text_edit = true - end - return text_edit - end, text_edits) - -- Some LSP servers are depending on the VSCode behavior. -- The VSCode will re-locate the cursor position after applying TextEdit so we also do it. local is_current_buf = vim.api.nvim_get_current_buf() == bufnr @@ -440,16 +420,35 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) -- Apply text edits. local is_cursor_fixed = false + local has_eol_text_edit = false for _, text_edit in ipairs(text_edits) do + -- Convert from LSP style ranges to Neovim style ranges. local e = { start_row = text_edit.range.start.line, - start_col = get_line_byte_from_position(bufnr, text_edit.range.start), + start_col = get_line_byte_from_position(bufnr, text_edit.range.start, offset_encoding), end_row = text_edit.range['end'].line, - end_col = get_line_byte_from_position(bufnr, text_edit.range['end']), + end_col = get_line_byte_from_position(bufnr, text_edit.range['end'], offset_encoding), text = vim.split(text_edit.newText, '\n', true), } + + -- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't accept it so we should fix it here. + local max = vim.api.nvim_buf_line_count(bufnr) + if max <= e.start_row or max <= e.end_row then + local len = #(get_line(bufnr, max - 1) or '') + if max <= e.start_row then + e.start_row = max - 1 + e.start_col = len + table.insert(e.text, 1, '') + end + if max <= e.end_row then + e.end_row = max - 1 + e.end_col = len + end + has_eol_text_edit = true + end vim.api.nvim_buf_set_text(bufnr, e.start_row, e.start_col, e.end_row, e.end_col, e.text) + -- Fix cursor position. local row_count = (e.end_row - e.start_row) + 1 if e.end_row < cursor.row then cursor.row = cursor.row + (#e.text - row_count) @@ -464,10 +463,13 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) end end + local max = vim.api.nvim_buf_line_count(bufnr) + + -- Apply fixed cursor position. if is_cursor_fixed then local is_valid_cursor = true - is_valid_cursor = is_valid_cursor and cursor.row < vim.api.nvim_buf_line_count(bufnr) - is_valid_cursor = is_valid_cursor and cursor.col <= #(vim.api.nvim_buf_get_lines(bufnr, cursor.row, cursor.row + 1, false)[1] or '') + is_valid_cursor = is_valid_cursor and cursor.row < max + is_valid_cursor = is_valid_cursor and cursor.col <= #(get_line(bufnr, max - 1) or '') if is_valid_cursor then vim.api.nvim_win_set_cursor(0, { cursor.row + 1, cursor.col }) end @@ -476,7 +478,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) -- Remove final line if needed local fix_eol = has_eol_text_edit fix_eol = fix_eol and api.nvim_buf_get_option(bufnr, 'fixeol') - fix_eol = fix_eol and (vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '') == '' + fix_eol = fix_eol and get_line(bufnr, max - 1) == '' if fix_eol then vim.api.nvim_buf_set_lines(bufnr, -2, -1, false, {}) end @@ -514,9 +516,12 @@ end ---@param text_document_edit table: a `TextDocumentEdit` object ---@param index number: Optional index of the edit, if from a list of edits (or nil, if not from a list) ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit -function M.apply_text_document_edit(text_document_edit, index) +function M.apply_text_document_edit(text_document_edit, index, offset_encoding) local text_document = text_document_edit.textDocument local bufnr = vim.uri_to_bufnr(text_document.uri) + if offset_encoding == nil then + vim.notify_once("apply_text_document_edit must be called with valid offset encoding", vim.log.levels.WARN) + end -- For lists of text document edits, -- do not check the version after the first edit. @@ -535,7 +540,7 @@ function M.apply_text_document_edit(text_document_edit, index) return end - M.apply_text_edits(text_document_edit.edits, bufnr) + M.apply_text_edits(text_document_edit.edits, bufnr, offset_encoding) end --- Parses snippets in a completion entry. @@ -734,7 +739,10 @@ end --- ---@param workspace_edit (table) `WorkspaceEdit` --see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit -function M.apply_workspace_edit(workspace_edit) +function M.apply_workspace_edit(workspace_edit, offset_encoding) + if offset_encoding == nil then + vim.notify_once("apply_workspace_edit must be called with valid offset encoding", vim.log.levels.WARN) + end if workspace_edit.documentChanges then for idx, change in ipairs(workspace_edit.documentChanges) do if change.kind == "rename" then @@ -750,7 +758,7 @@ function M.apply_workspace_edit(workspace_edit) elseif change.kind then error(string.format("Unsupported change: %q", vim.inspect(change))) else - M.apply_text_document_edit(change, idx) + M.apply_text_document_edit(change, idx, offset_encoding) end end return @@ -839,7 +847,8 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers local active_hl local active_signature = signature_help.activeSignature or 0 -- If the activeSignature is not inside the valid range, then clip it. - if active_signature >= #signature_help.signatures then + -- In 3.15 of the protocol, activeSignature was allowed to be negative + if active_signature >= #signature_help.signatures or active_signature < 0 then active_signature = 0 end local signature = signature_help.signatures[active_signature + 1] @@ -980,12 +989,16 @@ end --- Jumps to a location. --- ----@param location (`Location`|`LocationLink`) +---@param location table (`Location`|`LocationLink`) +---@param offset_encoding string utf-8|utf-16|utf-32 (required) ---@returns `true` if the jump succeeded -function M.jump_to_location(location) +function M.jump_to_location(location, offset_encoding) -- location may be Location or LocationLink local uri = location.uri or location.targetUri if uri == nil then return end + if offset_encoding == nil then + vim.notify_once("jump_to_location must be called with valid offset encoding", vim.log.levels.WARN) + end local bufnr = vim.uri_to_bufnr(uri) -- Save position in jumplist vim.cmd "normal! m'" @@ -1000,7 +1013,7 @@ function M.jump_to_location(location) api.nvim_buf_set_option(bufnr, 'buflisted', true) local range = location.range or location.targetSelectionRange local row = range.start.line - local col = get_line_byte_from_position(bufnr, range.start) + local col = get_line_byte_from_position(bufnr, range.start, offset_encoding) api.nvim_win_set_cursor(0, {row + 1, col}) -- Open folds under the cursor vim.cmd("normal! zv") @@ -1509,11 +1522,13 @@ do --[[ References ]] --- ---@param bufnr number Buffer id ---@param references table List of `DocumentHighlight` objects to highlight - ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32", or nil. Defaults to `offset_encoding` of first client of `bufnr` + ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32". ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#documentHighlight function M.buf_highlight_references(bufnr, references, offset_encoding) - validate { bufnr = {bufnr, 'n', true} } - offset_encoding = offset_encoding or M._get_offset_encoding(bufnr) + validate { + bufnr = {bufnr, 'n', true}, + offset_encoding = { offset_encoding, 'string', false }; + } for _, reference in ipairs(references) do local start_line, start_char = reference["range"]["start"]["line"], reference["range"]["start"]["character"] local end_line, end_char = reference["range"]["end"]["line"], reference["range"]["end"]["character"] @@ -1546,9 +1561,14 @@ end) --- The result can be passed to the {list} argument of |setqflist()| or --- |setloclist()|. --- ----@param locations (table) list of `Location`s or `LocationLink`s +---@param locations table list of `Location`s or `LocationLink`s +---@param offset_encoding string offset_encoding for locations utf-8|utf-16|utf-32 ---@returns (table) list of items -function M.locations_to_items(locations) +function M.locations_to_items(locations, offset_encoding) + if offset_encoding == nil then + vim.notify_once("locations_to_items must be called with valid offset encoding", vim.log.levels.WARN) + end + local items = {} local grouped = setmetatable({}, { __index = function(t, k) @@ -1588,7 +1608,7 @@ function M.locations_to_items(locations) local pos = temp.start local row = pos.line local line = lines[row] or "" - local col = pos.character + local col = M._str_byteindex_enc(line, pos.character, offset_encoding) table.insert(items, { filename = filename, lnum = row + 1, @@ -1772,7 +1792,13 @@ function M._get_offset_encoding(bufnr) local offset_encoding for _, client in pairs(vim.lsp.buf_get_clients(bufnr)) do - local this_offset_encoding = client.offset_encoding or "utf-16" + if client.offset_encoding == nil then + vim.notify_once( + string.format("Client (id: %s) offset_encoding is nil. Do not unset offset_encoding.", client.id), + vim.log.levels.ERROR + ) + end + local this_offset_encoding = client.offset_encoding if not offset_encoding then offset_encoding = this_offset_encoding elseif offset_encoding ~= this_offset_encoding then @@ -1901,7 +1927,9 @@ end ---@returns (number, number) `offset_encoding` index of the character in line {row} column {col} in buffer {buf} function M.character_offset(buf, row, col, offset_encoding) local line = get_line(buf, row) - offset_encoding = offset_encoding or M._get_offset_encoding(buf) + if offset_encoding == nil then + vim.notify_once("character_offset must be called with valid offset encoding", vim.log.levels.WARN) + end -- If the col is past the EOL, use the line length. if col > #line then return _str_utfindex_enc(line, nil, offset_encoding) diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index ebed502c92..b3036ea679 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -138,6 +138,13 @@ function M.get_query(lang, query_name) end end +local query_cache = setmetatable({}, { + __index = function(tbl, key) + rawset(tbl, key, {}) + return rawget(tbl, key) + end +}) + --- Parse {query} as a string. (If the query is in a file, the caller --- should read the contents into a string before calling). --- @@ -151,17 +158,23 @@ end --- -` info.captures` also points to `captures`. --- - `info.patterns` contains information about predicates. --- ----@param lang The language ----@param query A string containing the query (s-expr syntax) +---@param lang string The language +---@param query string A string containing the query (s-expr syntax) --- ---@returns The query function M.parse_query(lang, query) language.require_language(lang) - local self = setmetatable({}, Query) - self.query = vim._ts_parse_query(lang, query) - self.info = self.query:inspect() - self.captures = self.info.captures - return self + local cached = query_cache[lang][query] + if cached then + return cached + else + local self = setmetatable({}, Query) + self.query = vim._ts_parse_query(lang, query) + self.info = self.query:inspect() + self.captures = self.info.captures + query_cache[lang][query] = self + return self + end end --- Gets the text corresponding to a given node diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim index f9978a6b00..c881133b72 100644 --- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -153,7 +153,7 @@ func s:StartDebug_internal(dict) if &columns < g:termdebug_wide let s:save_columns = &columns let &columns = g:termdebug_wide - " If we make the Vim window wider, use the whole left halve for the debug + " If we make the Vim window wider, use the whole left half for the debug " windows. let s:allleft = 1 endif @@ -1051,10 +1051,10 @@ func s:GetEvaluationExpression(range, arg) return expr endfunc -" clean up expression that may got in because of range +" clean up expression that may get in because of range " (newlines and surrounding whitespace) " As it can also be specified via ex-command for assignments this function -" may not change the "content" parts (like replacing contained spaces +" may not change the "content" parts (like replacing contained spaces) func s:CleanupExpr(expr) " replace all embedded newlines/tabs/... let expr = substitute(a:expr, '\_s', ' ', 'g') @@ -1084,7 +1084,7 @@ func s:HandleEvaluate(msg) \ ->substitute('.*value="\(.*\)"', '\1', '') \ ->substitute('\\"', '"', 'g') \ ->substitute('\\\\', '\\', 'g') - "\ multi-byte characters arrive in octal form, replace everthing but NULL values + "\ multi-byte characters arrive in octal form, replace everything but NULL values \ ->substitute('\\000', s:NullRepl, 'g') \ ->substitute('\\\o\o\o', {-> eval('"' .. submatch(0) .. '"')}, 'g') "\ Note: GDB docs also mention hex encodings - the translations below work diff --git a/runtime/syntax/git.vim b/runtime/syntax/git.vim index a8467edd43..bf013ce195 100644 --- a/runtime/syntax/git.vim +++ b/runtime/syntax/git.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: generic git output " Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2019 Dec 05 +" Last Change: 2022 Jan 05 if exists("b:current_syntax") finish @@ -12,12 +12,28 @@ syn sync minlines=50 syn include @gitDiff syntax/diff.vim -syn region gitHead start=/\%^/ end=/^$/ -syn region gitHead start=/\%(^commit\%( \x\{40\}\)\{1,\}\%(\s*(.*)\)\=$\)\@=/ end=/^$/ - -" For git reflog and git show ...^{tree}, avoid sync issues -syn match gitHead /^\d\{6\} \%(\w\{4} \)\=\x\{40\}\%( [0-3]\)\=\t.*/ -syn match gitHead /^\x\{40\} \x\{40}\t.*/ +syn region gitHead start=/\%^\%(tag \|tree \|object \)\@=/ end=/^$/ contains=@NoSpell +syn region gitHead start=/\%(^commit\%( \x\{4,\}\)\{1,\}\%(\s*(.*)\)\=$\)\@=/ end=/^$/ contains=@NoSpell +" git log --oneline +" minimize false positives by verifying contents of buffer +if getline(1) =~# '^\x\{7,\} ' && getline('$') =~# '^\x\{7,\} ' + syn match gitHashAbbrev /^\x\{7,\} \@=/ contains=@NoSpell +elseif getline(1) =~# '^[|\/\\_ ]\{-\}\*[|\/\\_ ]\{-\} \x\{7,\} ' + syn match gitHashAbbrev /^[|\/\\_ ]\{-\}\*[|\/\\_ ]\{-\} \zs\x\{7,\} \@=/ contains=@NoSpell +endif +" git log --graph +syn region gitGraph start=/\%(^[|\/\\_ ]*\*[|\/\\_ ]\{-\} commit\%( \x\{4,\}\)\{1,\}\%(\s*(.*)\)\=$\)\@=/ end=/^\%([|\/\\_ ]*$\)\@=/ contains=@NoSpell +" git blame --porcelain +syn region gitHead start=/\%(^\x\{40,\} \d\+ \d\+\%( \d\+\)\=$\)\@=/ end=/^\t\@=/ contains=@NoSpell +" git ls-tree +syn match gitMode /^\d\{6\}\%( \%(blob\|tree\) \x\{4,\}\t\)\@=/ nextgroup=gitType skipwhite contains=@NoSpell +" git ls-files --stage +syn match gitMode /^\d\{6\}\%( \x\{4,\} [0-3]\t\)\@=/ nextgroup=gitHashStage skipwhite contains=@NoSpell +" .git/HEAD, .git/refs/ +syn match gitKeyword /\%^ref: \@=/ nextgroup=gitReference skipwhite contains=@NoSpell +syn match gitHash /\%^\x\{40,}\%$/ skipwhite contains=@NoSpell +" .git/logs/ +syn match gitReflog /^\x\{40,\} \x\{40,\} .\{-\}\d\+\s-\d\{4\}\t.*/ skipwhite contains=@NoSpell,gitReflogOld syn region gitDiff start=/^\%(diff --git \)\@=/ end=/^\%(diff --\|$\)\@=/ contains=@gitDiff fold syn region gitDiff start=/^\%(@@ -\)\@=/ end=/^\%(diff --\%(git\|cc\|combined\) \|$\)\@=/ contains=@gitDiff @@ -25,35 +41,47 @@ syn region gitDiff start=/^\%(@@ -\)\@=/ end=/^\%(diff --\%(git\|cc\|combined\) syn region gitDiffMerge start=/^\%(diff --\%(cc\|combined\) \)\@=/ end=/^\%(diff --\|$\)\@=/ contains=@gitDiff syn region gitDiffMerge start=/^\%(@@@@* -\)\@=/ end=/^\%(diff --\|$\)\@=/ contains=@gitDiff syn match gitDiffAdded "^ \++.*" contained containedin=gitDiffMerge -syn match gitDiffAdded "{+.*+}" contained containedin=gitDiff +syn match gitDiffAdded "{+[^}]*+}" contained containedin=gitDiff syn match gitDiffRemoved "^ \+-.*" contained containedin=gitDiffMerge -syn match gitDiffRemoved "\[-.*-\]" contained containedin=gitDiff +syn match gitDiffRemoved "\[-[^]]*-\]" contained containedin=gitDiff + +syn match gitKeyword /^commit \@=/ contained containedin=gitHead nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitKeyword /^\%(object\|tree\|parent\|encoding\|gpgsig\%(-\w\+\)\=\|previous\) \@=/ contained containedin=gitHead nextgroup=gitHash skipwhite contains=@NoSpell +syn match gitKeyword /^Merge:/ contained containedin=gitHead nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitIdentityKeyword /^\%(author\|committer\|tagger\) \@=/ contained containedin=gitHead nextgroup=gitIdentity skipwhite contains=@NoSpell +syn match gitIdentityHeader /^\%(Author\|Commit\|Tagger\):/ contained containedin=gitHead nextgroup=gitIdentity skipwhite contains=@NoSpell +syn match gitDateHeader /^\%(AuthorDate\|CommitDate\|Date\):/ contained containedin=gitHead nextgroup=gitDate skipwhite contains=@NoSpell -syn match gitKeyword /^\%(object\|type\|tag\|commit\|tree\|parent\|encoding\)\>/ contained containedin=gitHead nextgroup=gitHash,gitType skipwhite -syn match gitKeyword /^\%(tag\>\|ref:\)/ contained containedin=gitHead nextgroup=gitReference skipwhite -syn match gitKeyword /^Merge:/ contained containedin=gitHead nextgroup=gitHashAbbrev skipwhite -syn match gitMode /^\d\{6\}\>/ contained containedin=gitHead nextgroup=gitType,gitHash skipwhite -syn match gitIdentityKeyword /^\%(author\|committer\|tagger\)\>/ contained containedin=gitHead nextgroup=gitIdentity skipwhite -syn match gitIdentityHeader /^\%(Author\|Commit\|Tagger\):/ contained containedin=gitHead nextgroup=gitIdentity skipwhite -syn match gitDateHeader /^\%(AuthorDate\|CommitDate\|Date\):/ contained containedin=gitHead nextgroup=gitDate skipwhite +syn match gitKeyword /^[*|\/\\_ ]\+\zscommit \@=/ contained containedin=gitGraph nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitKeyword /^[|\/\\_ ]\+\zs\%(object\|tree\|parent\|encoding\|gpgsig\%(-\w\+\)\=\|previous\) \@=/ contained containedin=gitGraph nextgroup=gitHash skipwhite contains=@NoSpell +syn match gitKeyword /^[|\/\\_ ]\+\zsMerge:/ contained containedin=gitGraph nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitIdentityKeyword /^[|\/\\_ ]\+\zs\%(author\|committer\|tagger\) \@=/ contained containedin=gitGraph nextgroup=gitIdentity skipwhite contains=@NoSpell +syn match gitIdentityHeader /^[|\/\\_ ]\+\zs\%(Author\|Commit\|Tagger\):/ contained containedin=gitGraph nextgroup=gitIdentity skipwhite contains=@NoSpell +syn match gitDateHeader /^[|\/\\_ ]\+\zs\%(AuthorDate\|CommitDate\|Date\):/ contained containedin=gitGraph nextgroup=gitDate skipwhite contains=@NoSpell -syn match gitReflogHeader /^Reflog:/ contained containedin=gitHead nextgroup=gitReflogMiddle skipwhite -syn match gitReflogHeader /^Reflog message:/ contained containedin=gitHead skipwhite -syn match gitReflogMiddle /\S\+@{\d\+} (/he=e-2 nextgroup=gitIdentity +syn match gitKeyword /^type \@=/ contained containedin=gitHead nextgroup=gitType skipwhite contains=@NoSpell +syn match gitKeyword /^\%(summary\|boundary\|filename\|\%(author\|committer\)-\%(time\|tz\)\) \@=/ contained containedin=gitHead skipwhite contains=@NoSpell +syn match gitKeyword /^tag \@=/ contained containedin=gitHead nextgroup=gitReference skipwhite contains=@NoSpell +syn match gitIdentityKeyword /^\%(author\|committer\)-mail \@=/ contained containedin=gitHead nextgroup=gitEmail skipwhite contains=@NoSpell +syn match gitReflogHeader /^Reflog:/ contained containedin=gitHead nextgroup=gitReflogMiddle skipwhite contains=@NoSpell +syn match gitReflogHeader /^Reflog message:/ contained containedin=gitHead skipwhite contains=@NoSpell +syn match gitReflogMiddle /\S\+@{\d\+} (/he=e-2 nextgroup=gitIdentity contains=@NoSpell -syn match gitDate /\<\u\l\l \u\l\l \d\=\d \d\d:\d\d:\d\d \d\d\d\d [+-]\d\d\d\d/ contained -syn match gitDate /-\=\d\+ [+-]\d\d\d\d\>/ contained -syn match gitDate /\<\d\+ \l\+ ago\>/ contained -syn match gitType /\<\%(tag\|commit\|tree\|blob\)\>/ contained nextgroup=gitHash skipwhite -syn match gitStage /\<\d\t\@=/ contained -syn match gitReference /\S\+\S\@!/ contained -syn match gitHash /\<\x\{40\}\>/ contained nextgroup=gitIdentity,gitStage,gitHash skipwhite -syn match gitHash /^\<\x\{40\}\>/ containedin=gitHead contained nextgroup=gitHash skipwhite -syn match gitHashAbbrev /\<\x\{4,40\}\>/ contained nextgroup=gitHashAbbrev skipwhite -syn match gitHashAbbrev /\<\x\{4,39\}\.\.\./he=e-3 contained nextgroup=gitHashAbbrev skipwhite +syn match gitIdentity /\S.\{-\} <[^>]*>/ contained nextgroup=gitDate skipwhite contains=@NoSpell +syn region gitEmail matchgroup=gitEmailDelimiter start=/</ end=/>/ keepend oneline contained containedin=gitIdentity contains=@NoSpell +syn match gitDate /\<\u\l\l \u\l\l \d\=\d \d\d:\d\d:\d\d \d\d\d\d [+-]\d\d\d\d/ contained contains=@NoSpell +syn match gitDate /-\=\d\+ [+-]\d\d\d\d\>/ contained contains=@NoSpell +syn match gitDate /\<\d\+ \l\+ ago\>/ contained contains=@NoSpell +syn match gitType /\<\%(tag\|commit\|tree\|blob\)\>/ contained nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitReference /\S\+\S\@!/ contained contains=@NoSpell +syn match gitHash /\<\x\{40,\}\>/ contained nextgroup=gitIdentity,gitHash skipwhite contains=@NoSpell +syn match gitReflogOld /^\x\{40,\} \@=/ contained nextgroup=gitReflogNew skipwhite contains=@NoSpell +syn match gitReflogNew /\<\x\{40,\} \@=/ contained nextgroup=gitIdentity skipwhite contains=@NoSpell +syn match gitHashAbbrev /\<\x\{4,\}\>/ contained nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitHashAbbrev /\<\x\{4,39\}\.\.\./he=e-3 contained nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitHashStage /\<\x\{4,\}\>/ contained nextgroup=gitStage skipwhite contains=@NoSpell +syn match gitStage /\<\d\t\@=/ contained contains=@NoSpell -syn match gitIdentity /\S.\{-\} <[^>]*>/ contained nextgroup=gitDate skipwhite -syn region gitEmail matchgroup=gitEmailDelimiter start=/</ end=/>/ keepend oneline contained containedin=gitIdentity syn match gitNotesHeader /^Notes:\ze\n / @@ -68,7 +96,10 @@ hi def link gitEmailDelimiter Delimiter hi def link gitEmail Special hi def link gitDate Number hi def link gitMode Number +hi def link gitHashStage gitHash hi def link gitHashAbbrev gitHash +hi def link gitReflogOld gitHash +hi def link gitReflogNew gitHash hi def link gitHash Identifier hi def link gitReflogMiddle gitReference hi def link gitReference Function diff --git a/runtime/syntax/gitcommit.vim b/runtime/syntax/gitcommit.vim index 63b1ce920f..42c8d4414f 100644 --- a/runtime/syntax/gitcommit.vim +++ b/runtime/syntax/gitcommit.vim @@ -2,71 +2,87 @@ " Language: git commit file " Maintainer: Tim Pope <vimNOSPAM@tpope.org> " Filenames: *.git/COMMIT_EDITMSG -" Last Change: 2019 Dec 05 +" Last Change: 2022 Jan 05 if exists("b:current_syntax") finish endif +scriptencoding utf-8 + syn case match syn sync minlines=50 +syn sync linebreaks=1 if has("spell") syn spell toplevel endif syn include @gitcommitDiff syntax/diff.vim -syn region gitcommitDiff start=/\%(^diff --\%(git\|cc\|combined\) \)\@=/ end=/^\%(diff --\|$\|#\)\@=/ fold contains=@gitcommitDiff +syn region gitcommitDiff start=/\%(^diff --\%(git\|cc\|combined\) \)\@=/ end=/^\%(diff --\|$\|@@\@!\|[^[:alnum:]\ +-]\S\@!\)\@=/ fold contains=@gitcommitDiff syn match gitcommitSummary "^.*\%<51v." contained containedin=gitcommitFirstLine nextgroup=gitcommitOverflow contains=@Spell syn match gitcommitOverflow ".*" contained contains=@Spell -syn match gitcommitBlank "^[^#].*" contained contains=@Spell +syn match gitcommitBlank "^.\+" contained contains=@Spell +syn match gitcommitFirstLine "\%^.*" nextgroup=gitcommitBlank,gitcommitComment skipnl + +let s:scissors = 0 +let s:l = search('^[#;@!$%^&|:] -\{24,\} >8 -\{24,\}$', 'cnW', '', 100) +if s:l == 0 + let s:l = line('$') +elseif getline(s:l)[0] !=# getline(s:l - 1)[0] + let s:scissors = 1 +endif +let s:comment = escape((matchstr(getline(s:l), '^[#;@!$%^&|:]\S\@!') . '#')[0], '^$.*[]~\"/') -if get(g:, "gitcommit_cleanup") is# "scissors" - syn match gitcommitFirstLine "\%^.*" nextgroup=gitcommitBlank skipnl - syn region gitcommitComment start=/^# -\+ >8 -\+$/ end=/\%$/ contains=gitcommitDiff +if s:scissors + let s:comment .= ' -\{24,\} >8 -\{24,\}$' + exe 'syn region gitcommitComment start="^' . s:comment . '" end="\%$" contains=gitcommitDiff' else - syn match gitcommitFirstLine "\%^[^#].*" nextgroup=gitcommitBlank skipnl - syn match gitcommitComment "^#.*" + exe 'syn match gitcommitComment "^' . s:comment . '.*"' endif +exe 'syn match gitcommitTrailers "\n\@<=\n\%([[:alnum:]-]\+\s*:.*\|(cherry picked from commit .*\)\%(\n\s.*\|\n[[:alnum:]-]\+\s*:.*\|\n(cherry picked from commit .*\)*\%(\n\n*\%(' . s:comment . '\)\|\n*\%$\)\@="' -syn match gitcommitHead "^\%(# .*\n\)\+#$" contained transparent -syn match gitcommitOnBranch "\%(^# \)\@<=On branch" contained containedin=gitcommitComment nextgroup=gitcommitBranch skipwhite -syn match gitcommitOnBranch "\%(^# \)\@<=Your branch .\{-\} '" contained containedin=gitcommitComment nextgroup=gitcommitBranch skipwhite +unlet s:l s:comment s:scissors + +syn match gitcommitTrailerToken "^[[:alnum:]-]\+\s*:" contained containedin=gitcommitTrailers + +syn match gitcommitHash "\<\x\{40,}\>" contains=@NoSpell display +syn match gitcommitOnBranch "\%(^. \)\@<=On branch" contained containedin=gitcommitComment nextgroup=gitcommitBranch skipwhite +syn match gitcommitOnBranch "\%(^. \)\@<=Your branch .\{-\} '" contained containedin=gitcommitComment nextgroup=gitcommitBranch skipwhite syn match gitcommitBranch "[^ ']\+" contained -syn match gitcommitNoBranch "\%(^# \)\@<=Not currently on any branch." contained containedin=gitcommitComment -syn match gitcommitHeader "\%(^# \)\@<=.*:$" contained containedin=gitcommitComment -syn region gitcommitAuthor matchgroup=gitCommitHeader start=/\%(^# \)\@<=\%(Author\|Committer\):/ end=/$/ keepend oneline contained containedin=gitcommitComment transparent -syn match gitcommitNoChanges "\%(^# \)\@<=No changes$" contained containedin=gitcommitComment +syn match gitcommitNoBranch "\%(^. \)\@<=Not currently on any branch." contained containedin=gitcommitComment +syn match gitcommitHeader "\%(^. \)\@<=\S.*[::]\%(\n^$\)\@!$" contained containedin=gitcommitComment +syn region gitcommitAuthor matchgroup=gitCommitHeader start=/\%(^. \)\@<=\%(Author\|Committer\|Date\):/ end=/$/ keepend oneline contained containedin=gitcommitComment transparent +syn match gitcommitHeader "\%(^. \)\@<=commit\%( \x\{40,\}$\)\@=" contained containedin=gitcommitComment nextgroup=gitcommitHash skipwhite +syn match gitcommitNoChanges "\%(^. \)\@<=No changes$" contained containedin=gitcommitComment -syn region gitcommitUntracked start=/^# Untracked files:/ end=/^#$\|^#\@!/ contains=gitcommitHeader,gitcommitHead,gitcommitUntrackedFile fold -syn match gitcommitUntrackedFile "\t\@<=.*" contained +syn match gitcommitType "\%(^.\t\)\@<=[^[:punct:][:space:]][^/::]*[^[:punct:][:space:]][::]\ze "he=e-1 contained containedin=gitcommitComment nextgroup=gitcommitFile skipwhite +syn match gitcommitFile ".\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitArrow +syn match gitcommitArrow " -> " contained nextgroup=gitcommitFile +syn match gitcommitUntrackedFile "\%(^.\t\)\@<=[^::/]*\%(/.*\)\=$" contained containedin=gitcommitComment -syn region gitcommitDiscarded start=/^# Change\%(s not staged for commit\|d but not updated\):/ end=/^#$\|^#\@!/ contains=gitcommitHeader,gitcommitHead,gitcommitDiscardedType fold -syn region gitcommitSelected start=/^# Changes to be committed:/ end=/^#$\|^#\@!/ contains=gitcommitHeader,gitcommitHead,gitcommitSelectedType fold -syn region gitcommitUnmerged start=/^# Unmerged paths:/ end=/^#$\|^#\@!/ contains=gitcommitHeader,gitcommitHead,gitcommitUnmergedType fold +syn region gitcommitUntracked start=/^\z(.\) Untracked files:$/ end=/^\z1\=$\|^\z1\@!/ contains=gitcommitHeader containedin=gitcommitComment containedin=gitcommitComment contained transparent fold +syn region gitcommitDiscarded start=/^\z(.\) Change\%(s not staged for commit\|d but not updated\):$/ end=/^\z1\=$\|^\z1\@!/ contains=gitcommitHeader,gitcommitDiscardedType containedin=gitcommitComment containedin=gitcommitComment contained transparent fold +syn region gitcommitSelected start=/^\z(.\) Changes to be committed:$/ end=/^\z1$\|^\z1\@!/ contains=gitcommitHeader,gitcommitSelectedType containedin=gitcommitComment containedin=gitcommitComment contained transparent fold +syn region gitcommitUnmerged start=/^\z(.\) Unmerged paths:$/ end=/^\z1\=$\|^\z1\@!/ contains=gitcommitHeader,gitcommitUnmergedType containedin=gitcommitComment containedin=gitcommitComment contained transparent fold +syn match gitcommitUntrackedFile "\%(^.\t\)\@<=.*" contained containedin=gitcommitUntracked -syn match gitcommitDiscardedType "\t\@<=[[:lower:]][^:]*[[:lower:]]: "he=e-2 contained containedin=gitcommitComment nextgroup=gitcommitDiscardedFile skipwhite -syn match gitcommitSelectedType "\t\@<=[[:lower:]][^:]*[[:lower:]]: "he=e-2 contained containedin=gitcommitComment nextgroup=gitcommitSelectedFile skipwhite -syn match gitcommitUnmergedType "\t\@<=[[:lower:]][^:]*[[:lower:]]: "he=e-2 contained containedin=gitcommitComment nextgroup=gitcommitUnmergedFile skipwhite -syn match gitcommitDiscardedFile ".\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitDiscardedArrow -syn match gitcommitSelectedFile ".\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitSelectedArrow -syn match gitcommitUnmergedFile ".\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitSelectedArrow +syn match gitcommitDiscardedType "\%(^.\t\)\@<=[^[:punct:][:space:]][^/::]*[^[:punct:][:space:]][::]\ze "he=e-1 contained nextgroup=gitcommitDiscardedFile skipwhite +syn match gitcommitSelectedType "\%(^.\t\)\@<=[^[:punct:][:space:]][^/::]*[^[:punct:][:space:]][::]\ze "he=e-1 contained nextgroup=gitcommitSelectedFile skipwhite +syn match gitcommitUnmergedType "\%(^.\t\)\@<=[^[:punct:][:space:]][^/::]*[^[:punct:][:space:]][::]\ze "he=e-1 contained nextgroup=gitcommitUnmergedFile skipwhite +syn match gitcommitDiscardedFile "\S.\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitDiscardedArrow +syn match gitcommitSelectedFile "\S.\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitSelectedArrow +syn match gitcommitUnmergedFile "\S.\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitUnmergedArrow syn match gitcommitDiscardedArrow " -> " contained nextgroup=gitcommitDiscardedFile syn match gitcommitSelectedArrow " -> " contained nextgroup=gitcommitSelectedFile -syn match gitcommitUnmergedArrow " -> " contained nextgroup=gitcommitSelectedFile - -syn match gitcommitWarning "\%^[^#].*: needs merge$" nextgroup=gitcommitWarning skipnl -syn match gitcommitWarning "^[^#].*: needs merge$" nextgroup=gitcommitWarning skipnl contained -syn match gitcommitWarning "^\%(no changes added to commit\|nothing \%(added \)\=to commit\)\>.*\%$" +syn match gitcommitUnmergedArrow " -> " contained nextgroup=gitcommitUnmergedFile hi def link gitcommitSummary Keyword +hi def link gitcommitTrailerToken Label hi def link gitcommitComment Comment -hi def link gitcommitUntracked gitcommitComment -hi def link gitcommitDiscarded gitcommitComment -hi def link gitcommitSelected gitcommitComment -hi def link gitcommitUnmerged gitcommitComment +hi def link gitcommitHash Identifier hi def link gitcommitOnBranch Comment hi def link gitcommitBranch Special hi def link gitcommitNoBranch gitCommitBranch diff --git a/runtime/syntax/gitrebase.vim b/runtime/syntax/gitrebase.vim index bc6f34d1a7..13f157b005 100644 --- a/runtime/syntax/gitrebase.vim +++ b/runtime/syntax/gitrebase.vim @@ -2,7 +2,7 @@ " Language: git rebase --interactive " Maintainer: Tim Pope <vimNOSPAM@tpope.org> " Filenames: git-rebase-todo -" Last Change: 2019 Dec 06 +" Last Change: 2022 Jan 05 if exists("b:current_syntax") finish @@ -10,8 +10,10 @@ endif syn case match -syn match gitrebaseHash "\v<\x{7,}>" contained -syn match gitrebaseCommit "\v<\x{7,}>" nextgroup=gitrebaseSummary skipwhite +let s:c = escape((matchstr(getline('$'), '^[#;@!$%^&|:]\S\@!') . '#')[0], '^$.*[]~\"/') + +syn match gitrebaseHash "\v<\x{7,}>" contained contains=@NoSpell +syn match gitrebaseCommit "\v<\x{7,}>" nextgroup=gitrebaseSummary skipwhite contains=@NoSpell syn match gitrebasePick "\v^p%(ick)=>" nextgroup=gitrebaseCommit skipwhite syn match gitrebaseReword "\v^r%(eword)=>" nextgroup=gitrebaseCommit skipwhite syn match gitrebaseEdit "\v^e%(dit)=>" nextgroup=gitrebaseCommit skipwhite @@ -26,12 +28,15 @@ syn match gitrebaseLabel "\v^l(abel)=>" nextgroup=gitrebaseName skipwhite syn match gitrebaseReset "\v^(t|reset)=>" nextgroup=gitrebaseName skipwhite syn match gitrebaseSummary ".*" contains=gitrebaseHash contained syn match gitrebaseCommand ".*" contained -syn match gitrebaseComment "^\s*#.*" contains=gitrebaseHash +exe 'syn match gitrebaseComment " \@<=' . s:c . ' empty$" containedin=gitrebaseSummary contained' +exe 'syn match gitrebaseComment "^\s*' . s:c . '.*" contains=gitrebaseHash' syn match gitrebaseSquashError "\v%^%(s%(quash)=>|f%(ixup)=>)" nextgroup=gitrebaseCommit skipwhite syn match gitrebaseMergeOption "\v-[Cc]>" nextgroup=gitrebaseMergeCommit skipwhite contained syn match gitrebaseMergeCommit "\v<\x{7,}>" nextgroup=gitrebaseName skipwhite contained syn match gitrebaseName "\v[^[:space:].*?i:^~/-]\S+" nextgroup=gitrebaseMergeComment skipwhite contained -syn match gitrebaseMergeComment "#" nextgroup=gitrebaseSummary skipwhite contained +exe 'syn match gitrebaseMergeComment "' . s:c . '" nextgroup=gitrebaseSummary skipwhite contained' + +unlet s:c hi def link gitrebaseCommit gitrebaseHash hi def link gitrebaseHash Identifier diff --git a/runtime/syntax/i3config.vim b/runtime/syntax/i3config.vim index a8b6637140..f9e15d57e5 100644 --- a/runtime/syntax/i3config.vim +++ b/runtime/syntax/i3config.vim @@ -1,8 +1,9 @@ " Vim syntax file " Language: i3 config file -" Maintainer: Mohamed Boughaba <mohamed dot bgb at gmail dot com> +" Original Author: Mohamed Boughaba <mohamed dot bgb at gmail dot com> +" Maintainer: Quentin Hibon (github user hiqua) " Version: 0.4 -" Last Change: 2021 Dec 14 +" Last Change: 2022 Jan 04 " References: " http://i3wm.org/docs/userguide.html#configuring diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 60bf393085..2b5b47c0b3 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -4437,11 +4437,7 @@ static void nv_ident(cmdarg_T *cap) // Start insert mode in terminal buffer restart_edit = 'i'; - add_map((char_u *)"<buffer> <esc> <Cmd>call jobstop(&channel)<CR>", TERM_FOCUS, true); - do_cmdline_cmd("autocmd TermClose <buffer> " - " if !v:event.status |" - " exec 'bdelete! ' .. expand('<abuf>') |" - " endif"); + add_map((char_u *)"<buffer> <esc> <Cmd>bdelete!<CR>", TERM_FOCUS, true); } } diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 9429a06e92..1296d410f6 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -4082,7 +4082,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // char, e.g., "thes," -> "these". p = fword + sp->ts_fidx; MB_PTR_BACK(fword, p); - if (!spell_iswordp(p, curwin)) { + if (!spell_iswordp(p, curwin) && *preword != NUL) { p = preword + STRLEN(preword); MB_PTR_BACK(preword, p); if (spell_iswordp(p, curwin)) { diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 45a10cc193..6e92baa939 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -1096,4 +1096,54 @@ func Test_git_file() filetype off endfunc +func Test_foam_file() + filetype on + call assert_true(mkdir('0', 'p')) + call assert_true(mkdir('0.orig', 'p')) + + call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict') + split Xfile1Dict + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict.something') + split Xfile1Dict.something + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties') + split XfileProperties + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something') + split XfileProperties.something + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties') + split XfileProperties + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something') + split XfileProperties.something + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], '0/Xfile') + split 0/Xfile + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], '0.orig/Xfile') + split 0.orig/Xfile + call assert_equal('foam', &filetype) + bwipe! + + call delete('0', 'rf') + call delete('0.orig', 'rf') + filetype off +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim index d8d5797dcf..c568805f87 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -590,4 +590,12 @@ func Test_match_char_class_upper() bwipe! endfunc +func Test_match_invalid_byte() + call writefile(0z630a.765d30aa0a.2e0a.790a.4030, 'Xinvalid') + new + source Xinvalid + bwipe! + call delete('Xinvalid') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index cf0faeee31..1ecb5c8070 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -768,6 +768,14 @@ func Test_spell_screendump() call delete('XtestSpell') endfunc +func Test_spell_single_word() + new + silent! norm 0R00 + spell! ß + silent 0norm 0r$ Dvz= + bwipe! +endfunc + let g:test_data_aff1 = [ \"SET ISO8859-1", \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", diff --git a/test/functional/editor/K_spec.lua b/test/functional/editor/K_spec.lua index 40f36491e4..8ad81ac3d6 100644 --- a/test/functional/editor/K_spec.lua +++ b/test/functional/editor/K_spec.lua @@ -33,6 +33,29 @@ describe('K', function() feed('i'..test_file..'<ESC>K') retry(nil, nil, function() eq(1, eval('filereadable("'..test_file..'")')) end) eq({'fnord'}, eval("readfile('"..test_file.."')")) + -- Confirm that Neovim is still in terminal mode after K is pressed (#16692). + helpers.sleep(500) + eq('t', eval('mode()')) + feed('<space>') -- Any key, not just <space>, can be used here to escape. + eq('n', eval('mode()')) + end) + + it("<esc> kills the buffer for a running 'keywordprg' command", function() + helpers.source('set keywordprg=less') + eval('writefile(["hello", "world"], "' .. test_file .. '")') + feed('i' .. test_file .. '<esc>K') + eq('t', eval('mode()')) + -- Confirm that an arbitrary keypress doesn't escape (i.e., the process is + -- still running). If the process were no longer running, an arbitrary + -- keypress would escape. + helpers.sleep(500) + feed('<space>') + eq('t', eval('mode()')) + -- Confirm that <esc> kills the buffer for the running command. + local bufnr = eval('bufnr()') + feed('<esc>') + eq('n', eval('mode()')) + helpers.neq(bufnr, eval('bufnr()')) end) end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 2fe7eca260..eab520948f 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -76,7 +76,7 @@ local function fake_lsp_server_setup(test_name, timeout_ms, options) end; flags = { allow_incremental_sync = options.allow_incremental_sync or false; - debounce_text_changes = 0; + debounce_text_changes = options.debounce_text_changes or 0; }; on_exit = function(...) vim.rpcnotify(1, "exit", ...) @@ -929,7 +929,60 @@ describe('LSP', function() local client test_rpc_server { test_name = "basic_check_buffer_open_and_change_incremental"; - options = { allow_incremental_sync = true }; + options = { + allow_incremental_sync = true, + }; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + ]] + end; + on_init = function(_client) + client = _client + local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental") + eq(sync_kind, client.resolved_capabilities().text_document_did_change) + eq(true, client.resolved_capabilities().text_document_open_close) + exec_lua [[ + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) + ]] + end; + on_exit = function(code, signal) + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) + end; + on_handler = function(err, result, ctx) + if ctx.method == 'start' then + exec_lua [[ + vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { + "123boop"; + }) + ]] + client.notify('finish') + end + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then + client.stop() + end + end; + } + end) + it('should check the body and didChange incremental with debounce', function() + local expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; + } + local client + test_rpc_server { + test_name = "basic_check_buffer_open_and_change_incremental"; + options = { + allow_incremental_sync = true, + debounce_text_changes = 5 + }; on_setup = function() exec_lua [[ BUFFER = vim.api.nvim_create_buf(false, true) @@ -1238,7 +1291,7 @@ describe('LSP', function() make_edit(2, 0, 2, 2, {"3"}); make_edit(3, 2, 3, 4, {""}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ '123First line of text'; '2econd line of text'; @@ -1258,7 +1311,7 @@ describe('LSP', function() make_edit(3, #'', 3, #"Fourth", {"another line of text", "before this"}); make_edit(3, #'Fourth', 3, #"Fourth line of text", {"!"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ ''; '123'; @@ -1282,7 +1335,7 @@ describe('LSP', function() make_edit(3, #"Fourth", 3, #'', {"another line of text", "before this"}); make_edit(3, #"Fourth line of text", 3, #'Fourth', {"!"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ ''; '123'; @@ -1299,7 +1352,7 @@ describe('LSP', function() local edits = { make_edit(4, 3, 4, 4, {"ä"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second line of text'; @@ -1312,7 +1365,7 @@ describe('LSP', function() local edits = { make_edit(5, 0, 5, 0, "foobar"); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second line of text'; @@ -1322,6 +1375,20 @@ describe('LSP', function() 'foobar'; }, buf_lines(1)) end) + it('applies multiple text edits at the end of the document', function() + local edits = { + make_edit(4, 0, 5, 0, ""); + make_edit(5, 0, 5, 0, "foobar"); + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") + eq({ + 'First line of text'; + 'Second line of text'; + 'Third line of text'; + 'Fourth line of text'; + 'foobar'; + }, buf_lines(1)) + end) describe('cursor position', function() it('don\'t fix the cursor if the range contains the cursor', function() @@ -1329,7 +1396,7 @@ describe('LSP', function() local edits = { make_edit(1, 0, 1, 19, 'Second line of text') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second line of text'; @@ -1346,7 +1413,7 @@ describe('LSP', function() make_edit(1, 0, 1, 6, ''), make_edit(1, 6, 1, 19, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; ''; @@ -1363,7 +1430,7 @@ describe('LSP', function() make_edit(1, 0, 1, 6, ''), make_edit(0, 18, 5, 0, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; }, buf_lines(1)) @@ -1375,7 +1442,7 @@ describe('LSP', function() local edits = { make_edit(1, 0, 2, 0, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Third line of text'; @@ -1390,7 +1457,7 @@ describe('LSP', function() local edits = { make_edit(1, 7, 1, 11, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second of text'; @@ -1406,7 +1473,7 @@ describe('LSP', function() local edits = { make_edit(0, 11, 1, 12, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Third line of text'; @@ -1422,21 +1489,21 @@ describe('LSP', function() local edits = { make_edit(0, 0, 5, 0, {"All replaced"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({'All replaced'}, buf_lines(1)) end) it('applies edits when the end line is 2 larger than vim\'s', function() local edits = { make_edit(0, 0, 6, 0, {"All replaced"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({'All replaced'}, buf_lines(1)) end) it('applies edits with a column offset', function() local edits = { make_edit(0, 0, 5, 2, {"All replaced"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({'All replaced'}, buf_lines(1)) end) end) @@ -1464,7 +1531,7 @@ describe('LSP', function() ]] end) it('correctly goes ahead with the edit if all is normal', function() - exec_lua('vim.lsp.util.apply_text_document_edit(...)', text_document_edit(5)) + exec_lua("vim.lsp.util.apply_text_document_edit(..., nil, 'utf-16')", text_document_edit(5)) eq({ 'First ↥ 🤦 🦄 line of text'; '2nd line of è¯text'; @@ -1476,7 +1543,7 @@ describe('LSP', function() local bufnr = select(1, ...) local text_edit = select(2, ...) vim.lsp.util.buf_versions[bufnr] = 10 - vim.lsp.util.apply_text_document_edit(text_edit) + vim.lsp.util.apply_text_document_edit(text_edit, nil, 'utf-16') ]], target_bufnr, text_document_edit(0)) eq({ 'First ↥ 🤦 🦄 line of text'; @@ -1489,7 +1556,7 @@ describe('LSP', function() local args = {...} local versionedBuf = args[2] vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion - vim.lsp.util.apply_text_document_edit(args[1]) + vim.lsp.util.apply_text_document_edit(args[1], nil, 'utf-16') ]], edit, versionedBuf) end @@ -1515,17 +1582,36 @@ describe('LSP', function() describe('workspace_apply_edit', function() it('workspace/applyEdit returns ApplyWorkspaceEditResponse', function() - local expected = { - applied = true; - failureReason = nil; + local expected_handlers = { + {NIL, {}, {method="test", client_id=1}}; + } + test_rpc_server { + test_name = "basic_init"; + on_init = function(client, _) + client.stop() + end; + -- If the program timed out, then code will be nil. + on_exit = function(code, signal) + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) + end; + -- Note that NIL must be used here. + -- on_handler(err, method, result, client_id) + on_handler = function(...) + local expected = { + applied = true; + failureReason = nil; + } + eq(expected, exec_lua [[ + local apply_edit = { + label = nil; + edit = {}; + } + return vim.lsp.handlers['workspace/applyEdit'](nil, apply_edit, {client_id = TEST_RPC_CLIENT_ID}) + ]]) + eq(table.remove(expected_handlers), {...}) + end; } - eq(expected, exec_lua [[ - local apply_edit = { - label = nil; - edit = {}; - } - return vim.lsp.handlers['workspace/applyEdit'](nil, apply_edit) - ]]) end) end) @@ -1599,7 +1685,7 @@ describe('LSP', function() local workspace_edits = args[1] local target_bufnr = args[2] - vim.lsp.util.apply_workspace_edit(workspace_edits) + vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16') return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) ]], make_workspace_edit(edits), target_bufnr)) @@ -1621,7 +1707,7 @@ describe('LSP', function() local workspace_edits = args[1] local target_bufnr = args[2] - vim.lsp.util.apply_workspace_edit(workspace_edits) + vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16') return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) ]], make_workspace_edit(edits), target_bufnr)) @@ -1638,7 +1724,7 @@ describe('LSP', function() }, } } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) end) it('createFile does not touch file if it exists and ignoreIfExists is set', function() @@ -1656,7 +1742,7 @@ describe('LSP', function() }, } } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) eq('Dummy content', read_file(tmpfile)) end) @@ -1676,7 +1762,7 @@ describe('LSP', function() }, } } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) eq('', read_file(tmpfile)) end) @@ -1697,7 +1783,7 @@ describe('LSP', function() } } } - eq(true, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit)) + eq(true, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16')) eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) eq(false, exec_lua('return vim.api.nvim_buf_is_loaded(vim.fn.bufadd(...))', tmpfile)) end) @@ -1858,7 +1944,7 @@ describe('LSP', function() } }, } - return vim.lsp.util.locations_to_items(locations) + return vim.lsp.util.locations_to_items(locations, 'utf-16') ]] eq(expected, actual) end) @@ -1888,7 +1974,7 @@ describe('LSP', function() } }, } - return vim.lsp.util.locations_to_items(locations) + return vim.lsp.util.locations_to_items(locations, 'utf-16') ]] eq(expected, actual) end) @@ -2192,7 +2278,7 @@ describe('LSP', function() end local jump = function(msg) - eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg)) + eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg, "utf-16")) eq(target_bufnr, exec_lua[[return vim.fn.bufnr('%')]]) return { line = exec_lua[[return vim.fn.line('.')]], @@ -2266,6 +2352,27 @@ describe('LSP', function() end) end) + describe('lsp.util.convert_signature_help_to_markdown_lines', function() + it('can handle negative activeSignature', function() + local result = exec_lua[[ + local signature_help = { + activeParameter = 0, + activeSignature = -1, + signatures = { + { + documentation = "", + label = "TestEntity.TestEntity()", + parameters = {} + }, + } + } + return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'cs', {','}) + ]] + local expected = {'```cs', 'TestEntity.TestEntity()', '```', ''} + eq(expected, result) + end) + end) + describe('lsp.util.get_effective_tabstop', function() local function test_tabstop(tabsize, softtabstop) exec_lua(string.format([[ diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 911fa017ab..44a7f00d8b 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -167,6 +167,27 @@ void ui_refresh(void) eq('variable', ret) end) + it("supports caching queries", function() + local long_query = query:rep(100) + local first_run = exec_lua ([[ + local before = vim.loop.hrtime() + cquery = vim.treesitter.parse_query("c", ...) + local after = vim.loop.hrtime() + return after - before + ]], long_query) + + local subsequent_runs = exec_lua ([[ + local before = vim.loop.hrtime() + for i=1,100,1 do + cquery = vim.treesitter.parse_query("c", ...) + end + local after = vim.loop.hrtime() + return after - before + ]], long_query) + + assert.True(1000 * subsequent_runs < first_run) + end) + it('support query and iter by capture', function() insert(test_text) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 613b39c833..cbb3d6884f 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -203,8 +203,8 @@ set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc891 set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.20.1.tar.gz) set(TREESITTER_C_SHA256 ffcc2ef0eded59ad1acec9aec4f9b0c7dd209fc1a85d85f8b0e81298e3dddcc2) -set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.20.2.tar.gz) -set(TREESITTER_SHA256 2a0445f8172bbf83db005aedb4e893d394e2b7b33251badd3c94c2c5cc37c403) +set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/bf210f0c9ec7931c1a5f639461495db240aac149.tar.gz) +set(TREESITTER_SHA256 49c1f4d7de932f46bb0a68febb71627c43c110ff6cea6170f475c00270535ad7) if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) |