diff options
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/autoload/netrw.vim | 17 | ||||
-rw-r--r-- | runtime/autoload/provider/clipboard.vim | 4 | ||||
-rw-r--r-- | runtime/doc/autocmd.txt | 2 | ||||
-rw-r--r-- | runtime/doc/builtin.txt | 14 | ||||
-rw-r--r-- | runtime/doc/cmdline.txt | 22 | ||||
-rw-r--r-- | runtime/doc/editing.txt | 47 | ||||
-rw-r--r-- | runtime/doc/eval.txt | 7 | ||||
-rw-r--r-- | runtime/doc/filetype.txt | 2 | ||||
-rw-r--r-- | runtime/doc/news.txt | 16 | ||||
-rw-r--r-- | runtime/doc/options.txt | 14 | ||||
-rw-r--r-- | runtime/doc/pattern.txt | 2 | ||||
-rw-r--r-- | runtime/doc/recover.txt | 9 | ||||
-rw-r--r-- | runtime/doc/treesitter.txt | 10 | ||||
-rw-r--r-- | runtime/doc/vim_diff.txt | 14 | ||||
-rw-r--r-- | runtime/ftplugin/sh.vim | 17 | ||||
-rw-r--r-- | runtime/lua/vim/_editor.lua | 20 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/options.lua | 3 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/vimfn.lua | 12 | ||||
-rw-r--r-- | runtime/lua/vim/filetype.lua | 3 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/_snippet.lua | 500 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/_snippet_grammar.lua | 143 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 12 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 35 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter.lua | 12 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/_fold.lua | 92 |
25 files changed, 454 insertions, 575 deletions
diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim index 2dd5cf45bf..dcaa075608 100644 --- a/runtime/autoload/netrw.vim +++ b/runtime/autoload/netrw.vim @@ -3469,6 +3469,11 @@ fun! s:NetrwBookHistHandler(chg,curdir) echo "bookmarked the current directory" endif + try + call s:NetrwBookHistSave() + catch + endtry + elseif a:chg == 1 " change to the bookmarked directory " call Decho("(user: <".v:count."gb>) change to the bookmarked directory",'~'.expand("<slnum>")) @@ -3613,6 +3618,11 @@ fun! s:NetrwBookHistHandler(chg,curdir) " call Decho("g:netrw_bookmarklist=".string(g:netrw_bookmarklist),'~'.expand("<slnum>")) endif " call Decho("resulting g:netrw_bookmarklist=".string(g:netrw_bookmarklist),'~'.expand("<slnum>")) + + try + call s:NetrwBookHistSave() + catch + endtry endif call s:NetrwBookmarkMenu() call s:NetrwTgtMenu() @@ -7322,8 +7332,7 @@ fun! s:NetrwMarkFileDiff(islocal) exe "NetrwKeepj e ".fnameescape(fname) diffthis elseif cnt == 2 || cnt == 3 - vsplit - wincmd l + below vsplit " call Decho("diffthis: ".fname,'~'.expand("<slnum>")) exe "NetrwKeepj e ".fnameescape(fname) diffthis @@ -11934,9 +11943,9 @@ fun! s:NetrwBufRemover(bufid) " call Decho("buf#".a:bufid." has name <".bufname(a:bufid).">","~".expand("<slnum>")) " call Decho("buf#".a:bufid." has winid#".bufwinid(a:bufid),"~".expand("<slnum>")) - if a:bufid > 1 && !buflisted(a:bufid) && bufname(a:bufid) == "" && bufwinid(a:bufid) == -1 + if a:bufid > 1 && !buflisted(a:bufid) && bufloaded(a:bufid) && bufname(a:bufid) == "" && bufwinid(a:bufid) == -1 " call Decho("(s:NetrwBufRemover) removing buffer#".a:bufid,"~".expand("<slnum>")) - exe "bd! ".a:bufid + exe "sil! bd! ".a:bufid endif " call Dret("s:NetrwBufRemover") diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index 6ba28dcbfc..05f6bdb871 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -93,9 +93,9 @@ function! provider#clipboard#Executable() abort let s:cache_enabled = 0 return 'pbcopy' elseif !empty($WAYLAND_DISPLAY) && executable('wl-copy') && executable('wl-paste') - let s:copy['+'] = ['wl-copy', '--foreground', '--type', 'text/plain'] + let s:copy['+'] = ['wl-copy', '--type', 'text/plain'] let s:paste['+'] = ['wl-paste', '--no-newline'] - let s:copy['*'] = ['wl-copy', '--foreground', '--primary', '--type', 'text/plain'] + let s:copy['*'] = ['wl-copy', '--primary', '--type', 'text/plain'] let s:paste['*'] = ['wl-paste', '--no-newline', '--primary'] return 'wl-copy' elseif !empty($WAYLAND_DISPLAY) && executable('waycopy') && executable('waypaste') diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 90211fc5db..7ca9996e15 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -118,7 +118,7 @@ manually. Mostly the screen will not scroll up, thus there is no hit-enter prompt. When one command outputs two messages this can happen anyway. ============================================================================== -3. Removing autocommands *autocmd-remove* +3. Removing autocommands *autocmd!* *autocmd-remove* :au[tocmd]! [group] {event} {aupat} [++once] [++nested] {cmd} Remove all autocommands associated with {event} and diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index d0866b1c42..48fa953954 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -925,7 +925,7 @@ cosh({expr}) *cosh()* echo cosh(-0.5) < -1.127626 -count({comp}, {expr} [, {ic} [, {start}]]) *count()* +count({comp}, {expr} [, {ic} [, {start}]]) *count()* *E706* Return the number of times an item with value {expr} appears in |String|, |List| or |Dictionary| {comp}. @@ -5115,7 +5115,6 @@ printf({fmt}, {expr1} ...) *printf()* precision, the argument(s) to be used must also be specified using a {n$} positional argument specifier. See |printf-$|. - The conversion specifiers and their meanings are: *printf-d* *printf-b* *printf-B* *printf-o* *printf-x* *printf-X* @@ -5306,6 +5305,13 @@ printf({fmt}, {expr1} ...) *printf()* < E1505: Invalid format specifier: %1$d at width %2$d is: %01$*2$.3$d + *E1507* + This internal error indicates that the logic to parse a + positional format argument ran into a problem that couldn't be + otherwise reported. Please file a bug against Vim if you run + into this, copying the exact format string and parameters that + were used. + prompt_getprompt({buf}) *prompt_getprompt()* Returns the effective prompt text for buffer {buf}. {buf} can be a buffer name or number. See |prompt-buffer|. @@ -7842,8 +7848,8 @@ swapinfo({fname}) *swapinfo()* user user name host host name fname original file name - pid PID of the Vim process that created the swap - file + pid PID of the Nvim process that created the swap + file, or zero if not running. mtime last modification time in seconds inode Optional: INODE number of the file dirty 1 if file was modified, 0 if not diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index 5ee1a2af13..63967f84a1 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -493,16 +493,26 @@ example, to match only files that end in ".c": > :e *.c$ This will not match a file ending in ".cpp". Without the "$" it does match. -The old value of an option can be obtained by hitting 'wildchar' just after -the '='. For example, typing 'wildchar' after ":set dir=" will insert the -current value of 'dir'. This overrules file name completion for the options -that take a file name. - If you would like using <S-Tab> for CTRL-P in an xterm, put this command in your .cshrc: > xmodmap -e "keysym Tab = Tab Find" And this in your vimrc: > :cmap <Esc>[1~ <C-P> +< *complete-set-option* +When setting an option using |:set=|, the old value of an option can be +obtained by hitting 'wildchar' just after the '='. For example, typing +'wildchar' after ":set dir=" will insert the current value of 'dir'. This +overrules file name completion for the options that take a file name. + +When using |:set=|, |:set+=|, or |:set^=|, string options that have +pre-defined names or syntax (e.g. 'diffopt', 'listchars') or are a list of +single-character flags (e.g. 'shortmess') will also present a list of possible +values for completion when using 'wildchar'. + +When using |:set-=|, comma-separated options like 'diffopt' or 'backupdir' +will show each item separately. Flag list options like 'shortmess' will show +both the entire old value and the individual flags. Otherwise completion will +just fill in with the entire old value. ============================================================================== 3. Ex command-lines *cmdline-lines* @@ -732,7 +742,7 @@ An example for subtracting (which isn't very useful): > On this text: 1 one ~ 2 two ~ - 3 three FOLDED~ + 3 three FOLDED ~ 4 four FOLDED ~ 5 five FOLDED ~ 6 six FOLDED ~ diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 6e7963c066..7df2eb9742 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -169,33 +169,26 @@ If you want to keep the changed buffer without saving it, switch on the 2. Editing a file *edit-a-file* *:e* *:edit* *reload* -:e[dit] [++opt] [+cmd] Edit the current file. This is useful to re-edit the +:e[dit][!] [++opt] [+cmd] + Edit the current file. This is useful to re-edit the current file, when it has been changed outside of Vim. - This fails when changes have been made to the current - buffer and 'autowriteall' isn't set or the file can't - be written. - Also see |++opt| and |+cmd|. - *:edit!* *discard* -:e[dit]! [++opt] [+cmd] - Edit the current file always. Discard any changes to - the current buffer. This is useful if you want to - start all over again. + If [!] is given, unsaved changes in the current buffer + are discarded. Without [!] the command fails if there + are unsaved changes, unless 'autowriteall' is set and + the file can be written. Also see |++opt| and |+cmd|. *:edit_f* -:e[dit] [++opt] [+cmd] {file} +:e[dit][!] [++opt] [+cmd] {file} Edit {file}. - This fails when changes have been made to the current - buffer, unless 'hidden' is set or 'autowriteall' is - set and the file can be written. - Also see |++opt| and |+cmd|. - *:edit!_f* -:e[dit]! [++opt] [+cmd] {file} - Edit {file} always. Discard any changes to the - current buffer. + If [!] is given, unsaved changes in the current buffer + are discarded. Without [!] the command fails if there + are unsaved changes, unless 'hidden' is set or + 'autowriteall' is set and the file can be written. Also see |++opt| and |+cmd|. + *:edit_#* *:e#* :e[dit] [++opt] [+cmd] #[count] Edit the [count]th buffer (as shown by |:files|). @@ -357,8 +350,8 @@ is to use "path\[[]abc]", this matches the file "path\[abc]". *starstar-wildcard* Expanding "**" is possible on Unix, Win32, macOS and a few other systems (but -it may depend on your 'shell' setting. It's known to work correctly for zsh; for -bash this requires at least bash version >= 4.X). +it may depend on your 'shell' setting on Unix and macOS. It's known to work +correctly for zsh; for bash this requires at least bash version >= 4.X). This allows searching a directory tree. This goes up to 100 directories deep. Note there are some commands where this works slightly differently, see |file-searching|. @@ -1078,8 +1071,8 @@ will get the ACL info of the original file. The ACL info is also used to check if a file is read-only (when opening the file). - *xattr* *E1506* *E1507* *E1508* *E1509* -xattr stands for Extended Attributes It is an advanced way to save metadata + *xattr* *E1506* *E1508* *E1509* +xattr stands for Extended Attributes. It is an advanced way to save metadata alongside the file in the filesystem. It depends on the actual filesystem being used and Vim supports it only on a Linux system. Vim attempts to preserve the extended attribute info when writing a file. @@ -1224,10 +1217,10 @@ MULTIPLE WINDOWS AND BUFFERS *window-exit* *:confirm* *:conf* :conf[irm] {command} Execute {command}, and use a dialog when an operation has to be confirmed. Can be used on the - |:q|, |:qa| and |:w| commands (the latter to override - a read-only setting), and any other command that can - fail in such a way, such as |:only|, |:buffer|, - |:bdelete|, etc. + |:edit|, |:q|, |:qa| and |:w| commands (the latter to + override a read-only setting), and any commands that + can fail because of unsaved changes, such as |:only|, + |:buffer|, |:bdelete|, etc. Examples: > :confirm w foo diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 3cd5ea13f6..c41237b862 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2276,12 +2276,13 @@ v:stderr |channel-id| corresponding to stderr. The value is always 2; :call chansend(v:stderr, "error: toaster empty\n") < *v:swapname* *swapname-variable* -v:swapname Only valid when executing |SwapExists| autocommands: Name of - the swap file found. Read-only. +v:swapname Name of the swapfile found. + Only valid during |SwapExists| event. + Read-only. *v:swapchoice* *swapchoice-variable* v:swapchoice |SwapExists| autocommands can set this to the selected choice - for handling an existing swap file: + for handling an existing swapfile: 'o' Open read-only 'e' Edit anyway 'r' Recover diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt index 27ec92dbc6..ed21dc1c37 100644 --- a/runtime/doc/filetype.txt +++ b/runtime/doc/filetype.txt @@ -396,7 +396,7 @@ So to enable this only for ruby, set the following variable: > :let g:ruby_exec = 1 If both, the global `plugin_exec` and the `<filetype>_exec` specific variable -are set, the filetpe specific variable should have precedent. +are set, the filetype specific variable should have precedent. AWK *ft-awk-plugin* diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index b632cf0932..b0f3b76587 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -68,6 +68,10 @@ The following changes may require adaptations in user config or plugins. • Float window support hide and show by setting `hide` on `nvim_open_win` and `nvim_win_set_config`. +• |vim.lsp.util.parse_snippet()| will now strictly follow the snippet grammar + defined by LSP, and hence previously parsed snippets might now be considered + invalid input. + ============================================================================== NEW FEATURES *news-features* @@ -105,9 +109,17 @@ The following new APIs and features were added. • |nvim_set_keymap()| and |nvim_del_keymap()| now support abbreviations. +• Better cmdline completion for string option value. |complete-set-option| + • Builtin TUI can now recognize "super" (|<D-|) and "meta" (|<T-|) modifiers in a terminal emulator that supports |tui-csiu|. +• Editor + • By default, the swapfile "ATTENTION" |E325| dialog is skipped if the + swapfile is owned by a running Nvim process, instead of prompting. If you + always want the swapfile dialog, delete the default SwapExists handler: + `autocmd! nvim_swapfile`. |default-autocmds| + • LSP • LSP method names are available in |vim.lsp.protocol.Methods|. • Implemented LSP inlay hints: |vim.lsp.inlay_hint()| @@ -143,6 +155,8 @@ The following new APIs and features were added. • Added `vim.treesitter.query.edit()`, for live editing of treesitter queries. • Improved error messages for query parsing. + • Added |vim.treesitter.foldtext()| to apply treesitter highlighting to + foldtext. • |vim.ui.open()| opens URIs using the system default handler (macOS `open`, Windows `explorer`, Linux `xdg-open`, etc.) @@ -189,6 +203,8 @@ The following changes to existing APIs or features add new behavior. option, which allows for rendering e.g., diagnostic severities differently. • Defaults: + • On Windows 'isfname' does not include ":". Drive letters are handled + correctly without it. (Use |gF| for filepaths suffixed with ":line:col"). • 'comments' includes "fb:•". • 'shortmess' includes the "C" flag. • Automatic linting of treesitter query files (see |ft-query-plugin|). diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 84cff775f6..283c1e3612 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -52,14 +52,16 @@ achieve special effects. These options come in three forms: 'lines' Warning: This may have a lot of side effects. - *:set-args* *E487* *E521* + *:set-args* *:set=* *E487* *E521* :se[t] {option}={value} or :se[t] {option}:{value} Set string or number option to {value}. For numeric options the value can be given in decimal, hex (preceded with 0x) or octal (preceded with '0'). The old value can be inserted by typing 'wildchar' (by - default this is a <Tab>). See |cmdline-completion|. + default this is a <Tab>). Many string options with + fixed syntax also support completing known values. + See |cmdline-completion| and |complete-set-option|. White space between {option} and '=' is allowed and will be ignored. White space between '=' and {value} is not allowed. @@ -93,6 +95,9 @@ achieve special effects. These options come in three forms: When the option is a list of flags, {value} must be exactly as they appear in the option. Remove flags one by one to avoid problems. + The individual values from a comma separated list or + list of flags can be inserted by typing 'wildchar'. + See |complete-set-option|. Also see |:set-args| above. The {option} arguments to ":set" may be repeated. For example: > @@ -1961,7 +1966,8 @@ A jump table for the options with a short description can be found at |Q_op|. When omitted a context of six lines is used. When using zero the context is actually one, since folds require a line in between, also - for a deleted line. + for a deleted line. Set it to a very large + value (999999) to disable folding completely. See |fold-diff|. iblank Ignore changes where lines are all blank. Adds @@ -3447,7 +3453,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'isfname'* *'isf'* 'isfname' 'isf' string (default for Windows: - "@,48-57,/,\,.,-,_,+,,,#,$,%,{,},[,],:,@-@,!,~,=" + "@,48-57,/,\,.,-,_,+,,,#,$,%,{,},[,],@-@,!,~,=" otherwise: "@,48-57,/,.,-,_,+,,,#,$,%,~,=") global The characters specified by this option are included in file names and diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt index d74757de4f..09119b0918 100644 --- a/runtime/doc/pattern.txt +++ b/runtime/doc/pattern.txt @@ -1065,7 +1065,7 @@ 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 + There can only be nine of these. You can use "\%(" to add more, but not counting it as a sub-expression. *E51* *E54* *E55* *E872* *E873* diff --git a/runtime/doc/recover.txt b/runtime/doc/recover.txt index b05bcd6bc8..e6b5b06744 100644 --- a/runtime/doc/recover.txt +++ b/runtime/doc/recover.txt @@ -83,6 +83,15 @@ Detecting an existing swap file ~ You can find this in the user manual, section |11.3|. + *W325* +The default |SwapExists| handler (|default-autocmds|) skips the |E325| prompt +(selects "(E)dit") if the swapfile owner process (1) is still running and (2) +was started by the current user. This presumes that you normally don't want +to be bothered with the |ATTENTION| message just because you happen to edit +the same file from multiple Nvim instances. In the worst case (a system +crash) there will be more than one swapfile for the file; use |:recover| to +inspect all of its swapfiles. + Updating the swapfile ~ diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 34971c7acf..e19fda8fd1 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -560,6 +560,16 @@ foldexpr({lnum}) *vim.treesitter.foldexpr()* Return: ~ (string) +foldtext() *vim.treesitter.foldtext()* + Returns the highlighted content of the first line of the fold or falls + back to |foldtext()| if no treesitter parser is found. Can be set directly + to 'foldtext': >lua + vim.wo.foldtext = 'v:lua.vim.treesitter.foldtext()' +< + + Return: ~ + `{ [1]: string, [2]: string[] }[]` | string + *vim.treesitter.get_captures_at_cursor()* get_captures_at_cursor({winnr}) Returns a list of highlight capture names under the cursor diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index e636746616..4161d3b21e 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -53,6 +53,8 @@ Defaults *nvim-defaults* - 'hlsearch' is enabled - 'include' defaults to "". The C ftplugin sets it to "^\\s*#\\s*include" - 'incsearch' is enabled +- 'isfname' does not include ":" (on Windows). Drive letters are handled + correctly without it. (Use |gF| for filepaths suffixed with ":line:col"). - 'joinspaces' is disabled - 'langnoremap' is enabled - 'langremap' is disabled @@ -139,6 +141,11 @@ nvim_terminal: nvim_cmdwin: - CmdwinEnter: Limits syntax sync to maxlines=1 in the |cmdwin|. +nvim_swapfile: +- SwapExists: Skips the swapfile prompt (sets |v:swapchoice| to "e") when the + swapfile is owned by a running Nvim process. Shows |W325| "Ignoring + swapfile…" message. + ============================================================================== New Features *nvim-features* @@ -287,13 +294,12 @@ Options: 'diffopt' "linematch" feature 'exrc' searches for ".nvim.lua", ".nvimrc", or ".exrc" files. The user is prompted whether to trust the file. - 'fillchars' flags: "msgsep", "horiz", "horizup", - "horizdown", "vertleft", "vertright", "verthoriz" + 'fillchars' flags: "msgsep", "horiz", "horizup", "horizdown", + "vertleft", "vertright", "verthoriz" 'foldcolumn' supports up to 9 dynamic/fixed columns 'guicursor' works in the terminal (TUI) 'inccommand' shows interactive results for |:substitute|-like commands and |:command-preview| commands - 'jumpoptions' "stack" behavior 'jumpoptions' "view" tries to restore the |mark-view| when moving through the |jumplist|, |changelist|, |alternate-file| or using |mark-motions|. 'laststatus' global statusline support @@ -304,6 +310,7 @@ Options: 'signcolumn' supports up to 9 dynamic/fixed columns 'statuscolumn' full control of columns using 'statusline' format 'tabline' %@Func@foo%X can call any function on mouse-click + 'termpastefilter' 'ttimeout', 'ttimeoutlen' behavior was simplified 'winblend' pseudo-transparency in floating windows |api-floatwin| 'winhighlight' window-local highlights @@ -382,6 +389,7 @@ Upstreamed features *nvim-upstreamed* These Nvim features were later integrated into Vim. - 'fillchars' flags: "eob" +- 'jumpoptions' "stack" behavior - 'wildoptions' flags: "pum" enables popupmenu for wildmode completion - |<Cmd>| - |WinClosed| diff --git a/runtime/ftplugin/sh.vim b/runtime/ftplugin/sh.vim index c227838d18..c1a6bc5ade 100644 --- a/runtime/ftplugin/sh.vim +++ b/runtime/ftplugin/sh.vim @@ -3,7 +3,8 @@ " Maintainer: Doug Kearns <dougkearns@gmail.com> " Previous Maintainer: Dan Sharp " Contributor: Enno Nagel <ennonagel+vim@gmail.com> -" Last Change: 2023 Aug 29 +" Eisuke Kawashima +" Last Change: 2023 Sep 28 if exists("b:did_ftplugin") finish @@ -39,16 +40,16 @@ if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") let b:undo_ftplugin ..= " | unlet! b:browsefilter" endif -if (exists("b:is_bash") && (b:is_bash == 1)) +if get(b:, "is_bash", 0) if !has("gui_running") && executable("less") - command! -buffer -nargs=1 Help silent exe '!bash -c "{ help "<args>" 2>/dev/null || man "<args>"; } | LESS= less"' | redraw! - elseif has('terminal') - command! -buffer -nargs=1 Help silent exe ':term bash -c "help "<args>" 2>/dev/null || man "<args>""' + command! -buffer -nargs=1 ShKeywordPrg silent exe '!bash -c "{ help "<args>" 2>/dev/null || man "<args>"; } | LESS= less"' | redraw! + elseif has("terminal") + command! -buffer -nargs=1 ShKeywordPrg silent exe ':term bash -c "help "<args>" 2>/dev/null || man "<args>""' else - command! -buffer -nargs=1 Help echo system('bash -c "help <args>" 2>/dev/null || man "<args>"') + command! -buffer -nargs=1 ShKeywordPrg echo system('bash -c "help <args>" 2>/dev/null || man "<args>"') endif - setlocal keywordprg=:Help - let b:undo_ftplugin ..= " | setl kp< | sil! delc -buffer Help" + setlocal keywordprg=:ShKeywordPrg + let b:undo_ftplugin ..= " | setl kp< | sil! delc -buffer ShKeywordPrg" endif let &cpo = s:save_cpo diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 7f09fc8038..bbe93bfbc8 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -548,6 +548,9 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive) else c2 = #bufline + 1 end + elseif regtype == 'V' then -- linewise selection, always return whole line + c1 = 0 + c2 = -1 else c1 = (l == pos1[1]) and pos1[2] or 0 c2 = (l == pos2[1]) and (pos2[2] + (inclusive and 1 or 0)) or -1 @@ -1147,11 +1150,28 @@ function vim._init_default_autocmds() end end, }) + vim.api.nvim_create_autocmd({ 'CmdwinEnter' }, { pattern = '[:>]', group = vim.api.nvim_create_augroup('nvim_cmdwin', {}), command = 'syntax sync minlines=1 maxlines=1', }) + + vim.api.nvim_create_autocmd({ 'SwapExists' }, { + pattern = '*', + group = vim.api.nvim_create_augroup('nvim_swapfile', {}), + callback = function() + local info = vim.fn.swapinfo(vim.v.swapname) + local user = vim.uv.os_get_passwd().username + local iswin = 1 == vim.fn.has('win32') + if info.error or info.pid <= 0 or (not iswin and info.user ~= user) then + vim.v.swapchoice = '' -- Show the prompt. + return + end + vim.v.swapchoice = 'e' -- Choose "(E)dit". + vim.notify(('W325: Ignoring swapfile from Nvim process %d'):format(info.pid)) + end, + }) end function vim._init_defaults() diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index ed0d844748..08ee1f97ba 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -1591,7 +1591,8 @@ vim.go.dex = vim.go.diffexpr --- When omitted a context of six lines is used. --- When using zero the context is actually one, --- since folds require a line in between, also ---- for a deleted line. +--- for a deleted line. Set it to a very large +--- value (999999) to disable folding completely. --- See `fold-diff`. --- --- iblank Ignore changes where lines are all blank. Adds diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 86e8781160..06de2fdd1c 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -6098,7 +6098,6 @@ function vim.fn.prevnonblank(lnum) end --- precision, the argument(s) to be used must also be specified --- using a {n$} positional argument specifier. See |printf-$|. --- ---- --- The conversion specifiers and their meanings are: --- --- *printf-d* *printf-b* *printf-B* *printf-o* *printf-x* *printf-X* @@ -6289,6 +6288,13 @@ function vim.fn.prevnonblank(lnum) end --- < E1505: Invalid format specifier: --- %1$d at width %2$d is: %01$*2$.3$d --- +--- *E1507* +--- This internal error indicates that the logic to parse a +--- positional format argument ran into a problem that couldn't be +--- otherwise reported. Please file a bug against Vim if you run +--- into this, copying the exact format string and parameters that +--- were used. +--- --- @param fmt any --- @param expr1? any --- @return any @@ -9311,8 +9317,8 @@ function vim.fn.swapfilelist() end --- user user name --- host host name --- fname original file name ---- pid PID of the Vim process that created the swap ---- file +--- pid PID of the Nvim process that created the swap +--- file, or zero if not running. --- mtime last modification time in seconds --- inode Optional: INODE number of the file --- dirty 1 if file was modified, 0 if not diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 5ae4e508ef..c412364461 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -565,6 +565,7 @@ local extension = { libsonnet = 'jsonnet', jsp = 'jsp', jl = 'julia', + just = 'just', kdl = 'kdl', kv = 'kivy', kix = 'kix', @@ -1331,6 +1332,7 @@ local filename = { ['.jsfmtrc'] = 'jsonc', ['.jshintrc'] = 'jsonc', ['.swrc'] = 'jsonc', + ['.justfile'] = 'just', Kconfig = 'kconfig', ['Kconfig.debug'] = 'kconfig', ['lftp.conf'] = 'lftp', @@ -1715,6 +1717,7 @@ local pattern = { ['org%.eclipse%..*%.prefs'] = 'jproperties', ['.*%.properties_.._.._.*'] = starsetf('jproperties'), ['[jt]sconfig.*%.json'] = 'jsonc', + ['[jJ]ustfile'] = 'just', ['Kconfig%..*'] = starsetf('kconfig'), ['.*%.[Ss][Uu][Bb]'] = 'krl', ['lilo%.conf.*'] = starsetf('lilo'), diff --git a/runtime/lua/vim/lsp/_snippet.lua b/runtime/lua/vim/lsp/_snippet.lua deleted file mode 100644 index e7ada5415f..0000000000 --- a/runtime/lua/vim/lsp/_snippet.lua +++ /dev/null @@ -1,500 +0,0 @@ -local P = {} - ----Take characters until the target characters (The escape sequence is '\' + char) ----@param targets string[] The character list for stop consuming text. ----@param specials string[] If the character isn't contained in targets/specials, '\' will be left. -P.take_until = function(targets, specials) - targets = targets or {} - specials = specials or {} - - return function(input, pos) - local new_pos = pos - local raw = {} - local esc = {} - while new_pos <= #input do - local c = string.sub(input, new_pos, new_pos) - if c == '\\' then - table.insert(raw, '\\') - new_pos = new_pos + 1 - c = string.sub(input, new_pos, new_pos) - if not vim.list_contains(targets, c) and not vim.list_contains(specials, c) then - table.insert(esc, '\\') - end - table.insert(raw, c) - table.insert(esc, c) - new_pos = new_pos + 1 - else - if vim.list_contains(targets, c) then - break - end - table.insert(raw, c) - table.insert(esc, c) - new_pos = new_pos + 1 - end - end - - if new_pos == pos then - return P.unmatch(pos) - end - - return { - parsed = true, - value = { - raw = table.concat(raw, ''), - esc = table.concat(esc, ''), - }, - pos = new_pos, - } - end -end - -P.unmatch = function(pos) - return { - parsed = false, - value = nil, - pos = pos, - } -end - -P.map = function(parser, map) - return function(input, pos) - local result = parser(input, pos) - if result.parsed then - return { - parsed = true, - value = map(result.value), - pos = result.pos, - } - end - return P.unmatch(pos) - end -end - -P.lazy = function(factory) - return function(input, pos) - return factory()(input, pos) - end -end - -P.token = function(token) - return function(input, pos) - local maybe_token = string.sub(input, pos, pos + #token - 1) - if token == maybe_token then - return { - parsed = true, - value = maybe_token, - pos = pos + #token, - } - end - return P.unmatch(pos) - end -end - -P.pattern = function(p) - return function(input, pos) - local maybe_match = string.match(string.sub(input, pos), '^' .. p) - if maybe_match then - return { - parsed = true, - value = maybe_match, - pos = pos + #maybe_match, - } - end - return P.unmatch(pos) - end -end - -P.many = function(parser) - return function(input, pos) - local values = {} - local new_pos = pos - while new_pos <= #input do - local result = parser(input, new_pos) - if not result.parsed then - break - end - table.insert(values, result.value) - new_pos = result.pos - end - if #values > 0 then - return { - parsed = true, - value = values, - pos = new_pos, - } - end - return P.unmatch(pos) - end -end - -P.any = function(...) - local parsers = { ... } - return function(input, pos) - for _, parser in ipairs(parsers) do - local result = parser(input, pos) - if result.parsed then - return result - end - end - return P.unmatch(pos) - end -end - -P.opt = function(parser) - return function(input, pos) - local result = parser(input, pos) - return { - parsed = true, - value = result.value, - pos = result.pos, - } - end -end - -P.seq = function(...) - local parsers = { ... } - return function(input, pos) - local values = {} - local new_pos = pos - for i, parser in ipairs(parsers) do - local result = parser(input, new_pos) - if result.parsed then - values[i] = result.value - new_pos = result.pos - else - return P.unmatch(pos) - end - end - return { - parsed = true, - value = values, - pos = new_pos, - } - end -end - -local Node = {} - -Node.Type = { - SNIPPET = 0, - TABSTOP = 1, - PLACEHOLDER = 2, - VARIABLE = 3, - CHOICE = 4, - TRANSFORM = 5, - FORMAT = 6, - TEXT = 7, -} - -function Node:__tostring() - local insert_text = {} - if self.type == Node.Type.SNIPPET then - for _, c in ipairs(self.children) do - table.insert(insert_text, tostring(c)) - end - elseif self.type == Node.Type.CHOICE then - table.insert(insert_text, self.items[1]) - elseif self.type == Node.Type.PLACEHOLDER then - for _, c in ipairs(self.children or {}) do - table.insert(insert_text, tostring(c)) - end - elseif self.type == Node.Type.TEXT then - table.insert(insert_text, self.esc) - end - return table.concat(insert_text, '') -end - ---@see https://code.visualstudio.com/docs/editor/userdefinedsnippets#_grammar - -local S = {} -S.dollar = P.token('$') -S.open = P.token('{') -S.close = P.token('}') -S.colon = P.token(':') -S.slash = P.token('/') -S.comma = P.token(',') -S.pipe = P.token('|') -S.plus = P.token('+') -S.minus = P.token('-') -S.question = P.token('?') -S.int = P.map(P.pattern('[0-9]+'), function(value) - return tonumber(value, 10) -end) -S.var = P.pattern('[%a_][%w_]+') -S.text = function(targets, specials) - return P.map(P.take_until(targets, specials), function(value) - return setmetatable({ - type = Node.Type.TEXT, - raw = value.raw, - esc = value.esc, - }, Node) - end) -end - -S.toplevel = P.lazy(function() - return P.any(S.placeholder, S.tabstop, S.variable, S.choice) -end) - -S.format = P.any( - P.map(P.seq(S.dollar, S.int), function(values) - return setmetatable({ - type = Node.Type.FORMAT, - capture_index = values[2], - }, Node) - end), - P.map(P.seq(S.dollar, S.open, S.int, S.close), function(values) - return setmetatable({ - type = Node.Type.FORMAT, - capture_index = values[3], - }, Node) - end), - P.map( - P.seq( - S.dollar, - S.open, - S.int, - S.colon, - S.slash, - P.any( - P.token('upcase'), - P.token('downcase'), - P.token('capitalize'), - P.token('camelcase'), - P.token('pascalcase') - ), - S.close - ), - function(values) - return setmetatable({ - type = Node.Type.FORMAT, - capture_index = values[3], - modifier = values[6], - }, Node) - end - ), - P.map( - P.seq( - S.dollar, - S.open, - S.int, - S.colon, - P.seq( - S.question, - P.opt(P.take_until({ ':' }, { '\\' })), - S.colon, - P.opt(P.take_until({ '}' }, { '\\' })) - ), - S.close - ), - function(values) - return setmetatable({ - type = Node.Type.FORMAT, - capture_index = values[3], - if_text = values[5][2] and values[5][2].esc or '', - else_text = values[5][4] and values[5][4].esc or '', - }, Node) - end - ), - P.map( - P.seq( - S.dollar, - S.open, - S.int, - S.colon, - P.seq(S.plus, P.opt(P.take_until({ '}' }, { '\\' }))), - S.close - ), - function(values) - return setmetatable({ - type = Node.Type.FORMAT, - capture_index = values[3], - if_text = values[5][2] and values[5][2].esc or '', - else_text = '', - }, Node) - end - ), - P.map( - P.seq( - S.dollar, - S.open, - S.int, - S.colon, - S.minus, - P.opt(P.take_until({ '}' }, { '\\' })), - S.close - ), - function(values) - return setmetatable({ - type = Node.Type.FORMAT, - capture_index = values[3], - if_text = '', - else_text = values[6] and values[6].esc or '', - }, Node) - end - ), - P.map( - P.seq(S.dollar, S.open, S.int, S.colon, P.opt(P.take_until({ '}' }, { '\\' })), S.close), - function(values) - return setmetatable({ - type = Node.Type.FORMAT, - capture_index = values[3], - if_text = '', - else_text = values[5] and values[5].esc or '', - }, Node) - end - ) -) - -S.transform = P.map( - P.seq( - S.slash, - P.take_until({ '/' }, { '\\' }), - S.slash, - P.many(P.any(S.format, S.text({ '$', '/' }, { '\\' }))), - S.slash, - P.opt(P.pattern('[ig]+')) - ), - function(values) - return setmetatable({ - type = Node.Type.TRANSFORM, - pattern = values[2].raw, - format = values[4], - option = values[6], - }, Node) - end -) - -S.tabstop = P.any( - P.map(P.seq(S.dollar, S.int), function(values) - return setmetatable({ - type = Node.Type.TABSTOP, - tabstop = values[2], - }, Node) - end), - P.map(P.seq(S.dollar, S.open, S.int, S.close), function(values) - return setmetatable({ - type = Node.Type.TABSTOP, - tabstop = values[3], - }, Node) - end), - P.map(P.seq(S.dollar, S.open, S.int, S.transform, S.close), function(values) - return setmetatable({ - type = Node.Type.TABSTOP, - tabstop = values[3], - transform = values[4], - }, Node) - end) -) - -S.placeholder = P.any( - P.map( - P.seq( - S.dollar, - S.open, - S.int, - S.colon, - P.opt(P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' })))), - S.close - ), - function(values) - return setmetatable({ - type = Node.Type.PLACEHOLDER, - tabstop = values[3], - -- insert empty text if opt did not match. - children = values[5] or { - setmetatable({ - type = Node.Type.TEXT, - raw = '', - esc = '', - }, Node), - }, - }, Node) - end - ) -) - -S.choice = P.map( - P.seq( - S.dollar, - S.open, - S.int, - S.pipe, - P.many(P.map(P.seq(S.text({ ',', '|' }), P.opt(S.comma)), function(values) - return values[1].esc - end)), - S.pipe, - S.close - ), - function(values) - return setmetatable({ - type = Node.Type.CHOICE, - tabstop = values[3], - items = values[5], - }, Node) - end -) - -S.variable = P.any( - P.map(P.seq(S.dollar, S.var), function(values) - return setmetatable({ - type = Node.Type.VARIABLE, - name = values[2], - }, Node) - end), - P.map(P.seq(S.dollar, S.open, S.var, S.close), function(values) - return setmetatable({ - type = Node.Type.VARIABLE, - name = values[3], - }, Node) - end), - P.map(P.seq(S.dollar, S.open, S.var, S.transform, S.close), function(values) - return setmetatable({ - type = Node.Type.VARIABLE, - name = values[3], - transform = values[4], - }, Node) - end), - P.map( - P.seq( - S.dollar, - S.open, - S.var, - S.colon, - P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))), - S.close - ), - function(values) - return setmetatable({ - type = Node.Type.VARIABLE, - name = values[3], - children = values[5], - }, Node) - end - ) -) - -S.snippet = P.map(P.many(P.any(S.toplevel, S.text({ '$' }, { '}', '\\' }))), function(values) - return setmetatable({ - type = Node.Type.SNIPPET, - children = values, - }, Node) -end) - -local M = {} - ----The snippet node type enum ----@types table<string, integer> -M.NodeType = Node.Type - ----Parse snippet string and returns the AST ----@param input string ----@return table -function M.parse(input) - local result = S.snippet(input, 1) - if not result.parsed then - error('snippet parsing failed.') - end - return result.value -end - -return M diff --git a/runtime/lua/vim/lsp/_snippet_grammar.lua b/runtime/lua/vim/lsp/_snippet_grammar.lua new file mode 100644 index 0000000000..86f00fea51 --- /dev/null +++ b/runtime/lua/vim/lsp/_snippet_grammar.lua @@ -0,0 +1,143 @@ +--- Grammar for LSP snippets, based on https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#snippet_syntax + +local lpeg = vim.lpeg +local P, S, R, V = lpeg.P, lpeg.S, lpeg.R, lpeg.V +local C, Cg, Ct = lpeg.C, lpeg.Cg, lpeg.Ct + +local M = {} + +local alpha = R('az', 'AZ') +local backslash = P('\\') +local colon = P(':') +local dollar = P('$') +local int = R('09') ^ 1 +local l_brace, r_brace = P('{'), P('}') +local pipe = P('|') +local slash = P('/') +local underscore = P('_') +local var = Cg((underscore + alpha) * ((underscore + alpha + int) ^ 0), 'name') +local format_capture = Cg(int / tonumber, 'capture') +local format_modifier = Cg(P('upcase') + P('downcase') + P('capitalize'), 'modifier') +local tabstop = Cg(int / tonumber, 'tabstop') + +--- Returns a function that unescapes occurrences of "special" characters. +--- +--- @param special string +--- @return fun(match: string): string +local function escape_text(special) + return function(match) + local escaped = match:gsub('\\(.)', function(c) + return special:find(c) and c or '\\' .. c + end) + return escaped + end +end + +-- Text nodes match "any character", but $, \, and } must be escaped. +local escapable = '$}\\' +local text = (backslash * S(escapable)) + (P(1) - S(escapable)) +local text_0, text_1 = (text ^ 0) / escape_text(escapable), text ^ 1 +-- Within choice nodes, \ also escapes comma and pipe characters. +local choice_text = C(((backslash * S(escapable .. ',|')) + (P(1) - S(escapable .. ',|'))) ^ 1) + / escape_text(escapable .. ',|') +local if_text, else_text = Cg(text_0, 'if_text'), Cg(text_0, 'else_text') +-- Within format nodes, make sure we stop at / +local format_text = C(((backslash * S(escapable)) + (P(1) - S(escapable .. '/'))) ^ 1) + / escape_text(escapable) +-- Within ternary condition format nodes, make sure we stop at : +local if_till_colon_text = Cg( + C(((backslash * S(escapable)) + (P(1) - S(escapable .. ':'))) ^ 1) / escape_text(escapable), + 'if_text' +) + +-- Matches the string inside //, allowing escaping of the closing slash. +local regex = Cg(((backslash * slash) + (P(1) - slash)) ^ 1, 'regex') + +-- Regex constructor flags (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/RegExp#parameters). +local options = Cg(S('dgimsuvy') ^ 0, 'options') + +--- @enum vim.snippet.Type +local Type = { + Tabstop = 1, + Placeholder = 2, + Choice = 3, + Variable = 4, + Format = 5, + Text = 6, + Snippet = 7, +} +M.NodeType = Type + +--- @class vim.snippet.Node<T>: { type: vim.snippet.Type, data: T } +--- @class vim.snippet.TabstopData: { tabstop: number } +--- @class vim.snippet.TextData: { text: string } +--- @class vim.snippet.PlaceholderData: { tabstop: vim.snippet.TabstopData, value: vim.snippet.Node<any> } +--- @class vim.snippet.ChoiceData: { tabstop: vim.snippet.TabstopData, values: string[] } +--- @class vim.snippet.VariableData: { name: string, default?: vim.snippet.Node<any>, regex?: string, format?: vim.snippet.Node<vim.snippet.FormatData|vim.snippet.TextData>[], options?: string } +--- @class vim.snippet.FormatData: { capture: number, modifier?: string, if_text?: string, else_text?: string } +--- @class vim.snippet.SnippetData: { children: vim.snippet.Node<any>[] } + +--- Returns a function that constructs a snippet node of the given type. +--- +--- @generic T +--- @param type vim.snippet.Type +--- @return fun(data: T): vim.snippet.Node<T> +local function node(type) + return function(data) + return { type = type, data = data } + end +end + +-- stylua: ignore +local G = P({ + 'snippet'; + snippet = Ct(Cg( + Ct(( + V('any') + + (Ct(Cg(text_1 / escape_text(escapable), 'text')) / node(Type.Text)) + ) ^ 1), 'children' + )) / node(Type.Snippet), + any_or_text = V('any') + (Ct(Cg(text_0 / escape_text(escapable), 'text')) / node(Type.Text)), + any = V('placeholder') + V('tabstop') + V('choice') + V('variable'), + tabstop = Ct(dollar * (tabstop + (l_brace * tabstop * r_brace))) / node(Type.Tabstop), + placeholder = Ct(dollar * l_brace * tabstop * colon * Cg(V('any_or_text'), 'value') * r_brace) / node(Type.Placeholder), + choice = Ct(dollar * + l_brace * + tabstop * + pipe * + Cg(Ct(choice_text * (P(',') * choice_text) ^ 0), 'values') * + pipe * + r_brace) / node(Type.Choice), + variable = Ct(dollar * ( + var + ( + l_brace * var * ( + r_brace + + (colon * Cg(V('any_or_text'), 'default') * r_brace) + + (slash * regex * slash * Cg(Ct((V('format') + (C(format_text) / node(Type.Text))) ^ 1), 'format') * slash * options * r_brace) + )) + )) / node(Type.Variable), + format = Ct(dollar * ( + format_capture + ( + l_brace * format_capture * ( + r_brace + + (colon * ( + (slash * format_modifier * r_brace) + + (P('+') * if_text * r_brace) + + (P('?') * if_till_colon_text * colon * else_text * r_brace) + + (P('-') * else_text * r_brace) + + (else_text * r_brace) + )) + )) + )) / node(Type.Format), +}) + +--- Parses the given input into a snippet tree. +--- @param input string +--- @return vim.snippet.Node<vim.snippet.SnippetData> +function M.parse(input) + local snippet = G:match(input) + assert(snippet, 'snippet parsing failed') + return snippet --- @type vim.snippet.Node<vim.snippet.SnippetData> +end + +return M diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 8a29fac2b5..a906512e24 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -652,7 +652,7 @@ local function on_code_action_results(results, ctx, options) -- arguments?: any[] -- ---@type lsp.Client - local client = vim.lsp.get_client_by_id(action_tuple[1]) + local client = assert(vim.lsp.get_client_by_id(action_tuple[1])) local action = action_tuple[2] local reg = client.dynamic_capabilities:get(ms.textDocument_codeAction, { bufnr = ctx.bufnr }) @@ -663,10 +663,14 @@ local function on_code_action_results(results, ctx, options) if not action.edit and client and supports_resolve then client.request(ms.codeAction_resolve, action, function(err, resolved_action) if err then - vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR) - return + if action.command then + apply_action(action, client) + else + vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR) + end + else + apply_action(resolved_action, client) end - apply_action(resolved_action, client) end, ctx.bufnr) else apply_action(action, client) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 51ed87219c..a4c8959b99 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1,5 +1,5 @@ local protocol = require('vim.lsp.protocol') -local snippet = require('vim.lsp._snippet') +local snippet = require('vim.lsp._snippet_grammar') local validate = vim.validate local api = vim.api local list_extend = vim.list_extend @@ -610,12 +610,41 @@ end ---@return string parsed snippet function M.parse_snippet(input) local ok, parsed = pcall(function() - return tostring(snippet.parse(input)) + return snippet.parse(input) end) if not ok then return input end - return parsed + + --- @param node vim.snippet.Node<any> + --- @return string + local function node_to_string(node) + local insert_text = {} + if node.type == snippet.NodeType.Snippet then + for _, child in + ipairs((node.data --[[@as vim.snippet.SnippetData]]).children) + do + table.insert(insert_text, node_to_string(child)) + end + elseif node.type == snippet.NodeType.Choice then + table.insert(insert_text, (node.data --[[@as vim.snippet.ChoiceData]]).values[1]) + elseif node.type == snippet.NodeType.Placeholder then + table.insert( + insert_text, + node_to_string((node.data --[[@as vim.snippet.PlaceholderData]]).value) + ) + elseif node.type == snippet.NodeType.Text then + table.insert( + insert_text, + node + .data --[[@as vim.snippet.TextData]] + .text + ) + end + return table.concat(insert_text) + end + + return node_to_string(parsed) end --- Sorts by CompletionItem.sortText. diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 0e34cbcbcc..f863942d3b 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -508,4 +508,16 @@ function M.foldexpr(lnum) return require('vim.treesitter._fold').foldexpr(lnum) end +--- Returns the highlighted content of the first line of the fold or falls back to |foldtext()| +--- if no treesitter parser is found. Can be set directly to 'foldtext': +--- +--- ```lua +--- vim.wo.foldtext = 'v:lua.vim.treesitter.foldtext()' +--- ``` +--- +---@return { [1]: string, [2]: string[] }[] | string +function M.foldtext() + return require('vim.treesitter._fold').foldtext() +end + return M diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 8bc08c9c2e..5c1cc06908 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -361,4 +361,96 @@ function M.foldexpr(lnum) return foldinfos[bufnr].levels[lnum] or '0' end +---@package +---@return { [1]: string, [2]: string[] }[]|string +function M.foldtext() + local foldstart = vim.v.foldstart + local bufnr = api.nvim_get_current_buf() + + ---@type boolean, LanguageTree + local ok, parser = pcall(ts.get_parser, bufnr) + if not ok then + return vim.fn.foldtext() + end + + local query = ts.query.get(parser:lang(), 'highlights') + if not query then + return vim.fn.foldtext() + end + + local tree = parser:parse({ foldstart - 1, foldstart })[1] + + local line = api.nvim_buf_get_lines(bufnr, foldstart - 1, foldstart, false)[1] + if not line then + return vim.fn.foldtext() + end + + ---@type { [1]: string, [2]: string[], range: { [1]: integer, [2]: integer } }[] | { [1]: string, [2]: string[] }[] + local result = {} + + local line_pos = 0 + + for id, node, metadata in query:iter_captures(tree:root(), 0, foldstart - 1, foldstart) do + local name = query.captures[id] + local start_row, start_col, end_row, end_col = node:range() + + local priority = tonumber(metadata.priority or vim.highlight.priorities.treesitter) + + if start_row == foldstart - 1 and end_row == foldstart - 1 then + -- check for characters ignored by treesitter + if start_col > line_pos then + table.insert(result, { + line:sub(line_pos + 1, start_col), + {}, + range = { line_pos, start_col }, + }) + end + line_pos = end_col + + local text = line:sub(start_col + 1, end_col) + table.insert(result, { text, { { '@' .. name, priority } }, range = { start_col, end_col } }) + end + end + + local i = 1 + while i <= #result do + -- find first capture that is not in current range and apply highlights on the way + local j = i + 1 + while + j <= #result + and result[j].range[1] >= result[i].range[1] + and result[j].range[2] <= result[i].range[2] + do + for k, v in ipairs(result[i][2]) do + if not vim.tbl_contains(result[j][2], v) then + table.insert(result[j][2], k, v) + end + end + j = j + 1 + end + + -- remove the parent capture if it is split into children + if j > i + 1 then + table.remove(result, i) + else + -- highlights need to be sorted by priority, on equal prio, the deeper nested capture (earlier + -- in list) should be considered higher prio + if #result[i][2] > 1 then + table.sort(result[i][2], function(a, b) + return a[2] < b[2] + end) + end + + result[i][2] = vim.tbl_map(function(tbl) + return tbl[1] + end, result[i][2]) + result[i] = { result[i][1], result[i][2] } + + i = i + 1 + end + end + + return result +end + return M |